HTML parsing quirks: what the browser actually builds when it reads your markup, and why the DOM in DevTools sometimes differs from what you typed. Understanding the parser saves hours of ‘it works in the validator but looks wrong on screen’ confusion.
Parser vs serializer
You write source HTML. The parser builds a DOM tree according to the HTML living standard – not according to XHTML habits or ‘what looks right’ in the editor. When you save or copy outerHTML, the serializer may emit different but equivalent markup (lower-case tags, implied tags added). Compare source, DOM, and inspector output when debugging.
Optional and implied tags
The parser inserts elements you omitted. A <html>, <head>, and <body> appear even in minimal documents. Certain start tags close previous open elements:
<p>First paragraph
<p>Second paragraph</p>
Many browsers parse that as two sibling paragraphs, not a paragraph inside a paragraph – the first <p> is closed implicitly when the second starts. Block elements inside <p> also close the paragraph:
<p>Text before
<div>Not allowed inside p in the DOM</div>
</p>
The DOM becomes <p>Text before</p><div>...</div>. Validators and the inspector disagree with your indentation – trust the DOM.
Tables and formatting elements
Missing <tbody> is inserted automatically. Content inside tables follows strict fostering rules – stray cells may move. Old habits like <b> and <i> parse fine; semantic tags are still preferred for meaning. Mis-nested <a> elements (an anchor wrapping another anchor) get split or repaired by the parser – avoid that pattern entirely.
Void elements and slashes
<img src="photo.jpg" alt="">
<br>
<input type="text" name="q">
Void elements have no closing tag and no contents. Trailing slashes from XHTML (<img />) are ignored in HTML. Unknown tags are treated as HTMLUnknownElement (or HTMLElement in some cases) and still render – custom elements rely on this before registration.
Raw text and escapable elements
Inside <script> and <style>, parsing rules change – </script> in a string can close the element early if you are careless. Inside <textarea> and <title>, character references decode but tags are text. Template contents in <template> are inert until cloned into the live DOM.
innerHTML and fragment parsing
Setting element.innerHTML = '<tr>...</tr>' on a <div> wraps table rows in implied <tbody> inside a <table> the parser invents. Use the correct context (<template>, insertAdjacentHTML on compatible parents) or DOM APIs to avoid surprise structure. Parsing is forgiving by design – that forgiveness creates DOMs you did not expect.
Practical habits
- Write explicit closing tags for non-void elements when clarity matters
- Do not put block-level elements inside
<p> - Use the validator, then confirm in the DOM inspector
- When CMS output looks wrong, view the parsed tree, not only the WYSIWYG
The parser is not your enemy – it keeps legacy pages working. Knowing its rules stops you fighting ghosts in the layout.

