HTML validation, templating and frameworks

Doctypes, validator errors worth acting on, how JSX and template languages map to the DOM, escaping and hydration.

Validating the document

A validator parses your document the way a browser does and reports where it had to guess. Since the parser silently repairs almost everything, invalid markup does not crash — it becomes a slightly different tree than the one you wrote, which is how bugs hide.

<!-- invalid: a block element cannot live inside a paragraph -->
<p>Read the <div>setup guide</div> first.</p>

<!-- valid: the parser keeps the structure you intended -->
<p>Read the <a href="/guides/setup/">setup guide</a> first.</p>
Validator messageTypical causeFix
Stray end tagExtra or mismatched closing tagMatch tags in reverse order, remove the duplicate
Element not allowed as child of elementBlock inside <p>, <div> inside <ul>Use the child the parent permits, or change the parent
Duplicate IDTwo elements share an idMake ids unique; use classes for styling hooks
Attribute not allowed on elementA property from another element guessed atCheck the element's attribute list
Source not allowed without captionsA <track> is missingAdd a captions track to the media element
Bad value for attributeUnquoted or malformed valueQuote the value and check its syntax
⚠️
A document can validate perfectly and still be unusable. The validator checks grammar, not meaning — a table used for page layout, or a div pretending to be a button, passes cleanly and still fails real users.

How template languages map to the DOM

JSX, single-file components, Jinja and Handlebars all end up producing the same thing: DOM nodes. What differs is the syntax and the escaping defaults, and those are exactly where the sharp edges are.

// template source
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>

// rendered DOM
<ul><li>Widget</li><li>Gadget</li></ul>
  • Reserved-word attributes get renamed: class becomes className and for becomes htmlFor in JSX.
  • Void elements must be self-closed in JSX (<img />), because the parser there is JavaScript, not HTML.
  • Interpolation is escaped by default; dangerouslySetInnerHTML and v-html opt out of that protection and must never receive user input.
  • Building markup by string concatenation re-opens the escaping problem on every release, including the closing-script sequence problem when embedding data into a page.

Escaping and hydration

Server rendering sends finished HTML, and the client then takes over the same tree — a process called hydration. It only works when both sides produce identical markup; a mismatch forces the framework to throw away the server's version of that subtree and re-render it, which is a visible flicker and a wasted pass.

// server renders 12:00:04, client renders 12:00:05 — mismatch
<p>{new Date().toLocaleTimeString()}</p>

// stable markup first, live value after mount
const [now, setNow] = useState(null);
useEffect(() => setNow(new Date()), []);
  • Anything nondeterministic during the first render will mismatch: current time, random values, locale-dependent date formatting and browser-only APIs.
  • Rendering null on the server and the real value in an effect is the standard fix.
  • Read framework warnings seriously — they name the exact element that differs.
  • Escaping output prevents injection, but it does not make stored HTML safe: sanitise rich text on the server before it is ever rendered.

FAQ

Does passing the validator improve my search ranking?
Not directly. It helps indirectly: invalid markup that the parser repairs can move content out of the place your template intended, and errors often signal other bugs on the same page.
Why does the validator complain about an ampersand in my URL?
A bare & starts an entity reference in HTML. Write it as &amp; inside attribute values and text.

The document head: metadata, favicons and social cards Accessibility foundations in markup

Last refreshed 2026-09-18.