This page contains intentionally broken HTML. Some of the mistakes below are errors and some are legal omissions, and in every case the parser builds a defined tree. The parser's error-recovery rules silently "fixes" each problem — but the fix may not be what you intended.
Each example shows the broken source code, the rendered result, and an explanation of what the browser actually did.
Source code:
<p>First paragraph.
<p>Second paragraph.
<p>Third paragraph.
Rendered result:
First paragraph.
Second paragraph.
Third paragraph.
What happened: The HTML spec says that a <p> element's end tag is optional. When the parser encounters a new <p>, it automatically closes the previous one. So this actually works correctly — but only because <p> has special rules. Not all elements are this forgiving.
Source code:
<ul>
<li>Apples
<li>Bananas
<li>Cherries
</ul>
Rendered result:
What happened: Like <p>, the <li> element has an optional end tag. The parser closes each <li> when it encounters the next one (or the closing </ul>). This is technically valid HTML5.
Source code:
<p>This text is <b>bold and <i>bold-italic</b> and just italic</i>.</p>
Rendered result:
This text is bold and bold-italic and just italic.
What happened: The tags overlap: <b> opens, then <i> opens, but </b> closes before </i>. This violates proper nesting. The browser uses the "adoption agency algorithm" to reconstruct a valid tree. It closes the <i> at the </b> boundary, then reopens an <i> for the remaining text. The visual result may look correct, but the DOM structure is different from what you wrote.
Source code:
<span>Before
<div>A div inside a span</div>
After</span>
Rendered result:
What happened: Nothing visible, and that is the lesson. A <div> is flow content and a <span> only accepts phrasing content, so this markup is invalid. The parser does not care: it keeps the <div> inside the <span> exactly as written. The tree matches your source and the page looks fine, which is why only a validator will tell you. Compare <p>Text <div>block</div></p>: there the parser does close the paragraph at the <div>, and the trailing </p> becomes a second, empty paragraph. Some invalid markup is repaired, some is kept; either way the browser shows you something.
<tbody>Source code:
<table>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 3</td><td>Cell 4</td></tr>
</table>
Rendered result:
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
What happened: Even though the source has no <tbody>, the browser inserts one automatically. Open DevTools and inspect the table — you will see a <tbody> wrapping the rows. This matters when you write CSS selectors like table > tr (which won't match, because the actual structure is table > tbody > tr) or when traversing the DOM with JavaScript.
<table>Source code:
<table>
Hello, I'm text inside a table!
<tr><td>Actual cell</td></tr>
</table>
Rendered result:
| Actual cell |
What happened: Text is not allowed directly inside <table>. The browser "fosters" the text out — it moves the stray text before the table in the DOM. Inspect the result and you will see the text node appears outside and above the <table> element, even though the source put it inside.
Source code:
<a href="#outer">Outer link <a href="#inner">inner link</a> still outer</a>
Rendered result:
What happened: The HTML spec forbids nesting <a> elements. When the parser encounters the second <a>, it closes the first one. The first link ends there, so the result is a link, a second link, and trailing text (“still outer”) that is no longer a link at all. Click each part and check the URL fragment to verify.
Click the button below to see the actual DOM the browser built for each example. Compare it to the source code above to see how error recovery changed the structure.