
Why Is the HTML Parser Secretly Mutating Your 'Valid' Markup?
An exploration of the 'Adoption Agency' and 'Foster Parenting' rules that allow the browser to surgically relocate your DOM nodes without warning.
You write a line of code, refresh the browser, and look at the Elements tab in DevTools only to find that your DOM tree looks nothing like your source file. It’s a gaslighting experience that every developer goes through eventually, usually right before they lose their mind over a CSS selector that simply refuses to fire.
The truth is, the HTML5 specification isn't just a list of tags; it’s a set of aggressive, highly opinionated "error recovery" algorithms. While JavaScript throws an error and dies when you misplace a semicolon, HTML tries to be "helpful" by silently rewriting your markup on the fly.
If you’ve ever wondered why your <div> suddenly jumped out of its parent or why your <b> tag is appearing twice, you’ve likely run into the Foster Parenting or Adoption Agency rules.
The Table That Refuses Guests: Foster Parenting
The HTML parser is particularly protective of <table> elements. According to the spec, a table has a very strict hierarchy: table -> tbody -> tr -> td. If you try to sneak something else in there—like a <div> or a <button>—the parser loses its mind.
Take this seemingly innocent snippet:
<table>
<div>I'm just trying to help!</div>
<tr>
<td>Cell 1</td>
</tr>
</table>You might expect the <div> to sit comfortably above the first row inside the table. It won't. The browser sees that <div> and realizes it's "misplaced" because it's not allowed as a direct child of a table.
Instead of throwing an error, the browser performs Foster Parenting. It snatches that <div> and moves it to the last element opened *before* the table.
What the DOM actually looks like:
<div>I'm just trying to help!</div>
<table>
<tbody>
<tr>
<td>Cell 1</td>
</tr>
</tbody>
</table>I've seen this break many layouts where a developer tried to use a <div> inside a table to show a loading state or a custom tooltip. Your CSS selectors targeting table > div will fail because, by the time the CSS engine looks at the page, that div is already a sibling of the table, not a child.
The Adoption Agency Algorithm (AAA)
This is where things get truly weird. The Adoption Agency Algorithm exists to handle "misnested" formatting tags (like <b>, <i>, <a>, or <span>).
In the early days of the web, people wrote terrible HTML. They would do things like this:
<b>Bold text <div>Wait, why is there a div here?</div> more bold text</b>Technically, a <div> (a block-level element) inside a <b> (an inline formatting element) is invalid. But the browser doesn't just move the div. Instead, it "closes" the bold tag before the div, and then "re-opens" a new bold tag inside or after the div to ensure the styling persists.
It "adopts" the formatting into the new structure. Here is how the browser reconstructs that mess:
<b>Bold text </b>
<div><b>Wait, why is there a div here?</b></div>
<b> more bold text</b>Notice that the <b> tag has been cloned. If you were counting on a single <b> element for a JavaScript querySelector or a specific CSS transition, you’re now dealing with three separate nodes.
Why This Kills React and Next.js Apps
If you’re working with modern frameworks, these parser mutations are more than just a curiosity—they are the primary cause of the dreaded Hydration Mismatch.
React builds a virtual representation of what it *thinks* the HTML should look like. When it sends that HTML to the browser, the browser's parser might "fix" it using the rules above. When React tries to "hydrate" (attach event listeners to) the existing DOM, it finds that the nodes don't match its internal map.
Common culprit: Nesting a `<div>` inside a `<p>`
// This looks fine in your component...
export default function Warning() {
return (
<p>
Caution:
<div>Don't do this!</div>
</p>
);
}The browser sees a block-level <div> inside a paragraph. Paragraphs are not allowed to contain block-level elements. The parser will automatically close the <p> tag before the <div> starts.
The resulting DOM:
<p>Caution:</p>
<div>Don't do this!</div>
<p></p> <!-- The phantom trailing paragraph -->React will scream at you in the console because it expected one <p> with a <div> inside, but it found two paragraphs and a sibling div.
How to Stay Sane
You can't turn these features off. They are baked into the core of how every modern browser (Chrome, Firefox, Safari) interprets the web. The only way to win is to play by the rules:
1. Validate your nesting: If you’re unsure if an element can go inside another, check MDN. Specifically, look for "Permitted content."
2. Tables are for data: Don't try to use <table> for layout or shove non-table elements inside them. If you need a wrapper inside a table, it *must* go inside a <td> or <th>.
3. Buttons and Links: Never put a <a> inside another <a>, or a <button> inside a <button>. The parser will violently eject the inner element.
4. Use Fragments: In React/Vue, use fragments (<>...</>) to group elements without adding extra nodes that might trigger a re-parenting rule.
The HTML parser is like a well-meaning but overbearing relative. It wants your page to look "right" even when your code is wrong, but its help usually comes at the cost of your architectural sanity. Keep your nesting valid, and you’ll keep your DOM nodes where you actually put them.

