# How the HTML parser repairs your markup, and why the repairs are the same in every browser

> A browser never rejects HTML. Whatever you paste, a tree comes out, and the HTML Standard says exactly which tree: the elements the parser creates for you, the end tags it supplies, the formatting elements it splits, the content it lifts out of tables, the comments it makes of your typos, and the quirks mode it falls into without a DOCTYPE. The 52 named parse errors, the five repairs worth knowing by name, what view source and inspect element disagree about, and why WCAG 2.2 could drop its parsing criterion without lowering the bar.

Source: https://ronutz.com/en/learn/how-the-html-parser-repairs-your-markup  
Updated: 2026-10-05  
Related tools: https://ronutz.com/en/tools/html-structure-explainer, https://ronutz.com/en/tools/css-specificity-calculator, https://ronutz.com/en/tools/xml-decoder

---

XML has a word for markup it will not read: not well-formed, stop, show an error. HTML has no such word. A browser given `<p>a<b>b<i>c</b>d</i>` does not complain; it builds a tree, paints it, and moves on, and the page author may never learn that the tree is not the one they wrote. For a long time each browser built a different tree from the same broken markup, and "works in one browser" was a sentence people said. The HTML Standard ended that by specifying the parser itself, including every recovery from every error: "The error handling for parse errors is well-defined (that's the processing rules described throughout this specification)". Since then there is one tree, the same in every conforming browser, and it can be shown. The [HTML structure & DOM explainer](https://ronutz.com/en/tools/html-structure-explainer) shows it, built by parse5, the same parser jsdom and Cheerio run, with each repair marked where the parser made it. This article is the field guide to those marks. The standard was read on 2026-10-05 and is quoted as it stands.

## A tree always comes out

The standard's position is stated in one sentence: "Parse errors are only errors with the syntax of HTML." An error is a point in the algorithm, not a verdict on the document. At each such point the specification says what the parser does next, and conformance checkers "must report at least one parse error condition to the user if one or more parse error conditions exist in the document". Fifty-two of these points have dedicated codes in a table (13.2.2), with names like `duplicate-attribute`, `eof-in-tag` and `non-void-html-element-start-tag-with-trailing-solidus`; the descriptions are non-normative but exact: "This error occurs if the parser encounters an attribute in a tag that already has an attribute with the same name. The parser ignores all such duplicate occurrences of the attribute." The errors of tree construction (a stray end tag, elements still open at the end of the file, a second `body`) are described in prose without codes, and a parser that reports them names them itself.

This is why "view source" and "inspect element" show different worlds. The source is what you wrote; the inspector is the tree the parser built. Every difference between them is one of a handful of repairs, and each has a name.

## The five repairs

**Elements the parser creates.** A document always has `html`, `head` and `body`, whether or not the tags were written: "Omitting an element's start tag in the situations described below does not mean the element is not present; it is implied, but it is still there." A `table` with rows but no `tbody` gets a `tbody`. In the explainer these elements carry no line number, because they have no position in the source; a dot sits in the gutter instead.

**End tags the parser supplies.** Nineteen elements may legally be left open: the standard's optional-tags section lists html, head, body, li, dt, dd, p, rt, rp, optgroup, option, colgroup, caption, thead, tbody, tfoot, tr, td and th, each with its condition. For `p`: "A p element's end tag may be omitted if the p element is immediately followed by an address, article, aside, blockquote, details, dialog, div, dl, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, main, menu, nav, ol, p, pre, search, section, table, or ul element, or if there is no more content in the parent element". So `<p>Hello<p>World` is two complete paragraphs, by design. But `<div><span>text</div>` also closes the span; the parser supplies the end tag there too, and for a `span` that is not sanctioned by any rule. The explainer draws the same mark on both, and its finding D13 names the elements whose supplied end tag means a missing or misnested tag.

**Formatting elements the parser splits.** `<p>a<b>b<i>c</b>d</i>` is the classic case. When `</b>` arrives, `i` is the innermost open element, not `b`. The standard's adoption agency algorithm closes `b` and, because `i` was open inside it and must stay open for the `d` that follows, reopens a copy of `i` after `b`. One `<i>` in the source becomes two `i` elements in the tree, both with the same start position. The result renders as the author probably meant, which is exactly why nobody notices until a style or a script addresses "the i element" and finds two.

**Content the parser lifts out of a table.** Inside a `table`, only table parts may appear: caption, colgroup, thead, tbody, tfoot, tr, td, th. Text or an element written directly inside the table, `<table><tr><td>x</td></tr>stray</table>`, cannot go into the table, so the standard's foster parenting rule inserts it before the table. In the tree, "stray" precedes the table it was written inside; in the rendering it appears above it, which is where people start searching for the bug.

**Typos the parser turns into comments or attributes.** `<!-->` is "an empty comment that is abruptly closed"; `<![CDATA[...]]>` outside SVG or MathML is treated as a comment; `<?xml version="1.0"?>` in HTML is a comment too. `<div id="a"class="b">` is read as if a space were there (`missing-whitespace-between-attributes`). `<div/>` does not close the div: "The trailing U+002F (/) in a start tag name can be used only in foreign content to specify self-closing tags. (Self-closing tags don't exist in HTML.) It is also allowed for void elements, but doesn't have any effect in this case." On the thirteen void elements (area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr) the slash is "unnecessary and has no effect of any kind"; on anything else it is an error and the element swallows what follows.

## The DOCTYPE and the two modes

Everything above happens inside a mode. The standard's parser begins in the "initial" insertion mode waiting for a DOCTYPE, and if anything else arrives first, "this is a parse error; if the parser cannot change the mode flag is false, set the Document to quirks mode." The standard explains the stakes without euphemism: "A DOCTYPE is a required preamble. DOCTYPEs are required for legacy reasons. When omitted, browsers tend to use a different rendering mode that is incompatible with some specifications." Quirks mode is the emulation of pre-standards browsers; a legacy DOCTYPE with a system identifier gives limited-quirks mode, almost standards with a few table-cell and inline-image behaviours kept. `<!DOCTYPE html>` is the fifteen characters that end the question.

## Why WCAG 2.2 could drop 4.1.1

WCAG 2.0 and 2.1 had a success criterion called Parsing, 4.1.1, Level A: "In content implemented using markup languages, elements have complete start and end tags, elements are nested according to their specifications, elements do not contain duplicate attributes, and any IDs are unique, except where the specifications allow these features." It existed because assistive technologies once parsed HTML themselves and broke on markup browsers survived. The current edition of WCAG 2.1 (W3C Recommendation 06 May 2025) keeps the sentence but adds a note that empties it: "This success criterion should be considered as always satisfied for any content using HTML or XML", because the HTML Living Standard now "requires that user agents support handling these cases consistently", and "in practice, this criterion no longer provides any benefit to people with disabilities in itself." WCAG 2.2 removed it, and its Understanding document says why: "Since WCAG 2.0 was published, the specifications (such as HTML) and browsers have improved their handling of parsing errors. It is also the case that assistive technology used to do their own parsing of markup, but now rely on the browser." Every assistive technology now reads the one tree the standard's parser builds. The bar did not move; the thing being measured did. What still matters is the structure that tree carries, and that is where the other criteria live: 1.1.1 Non-text Content ("All non-text content that is presented to the user has a text alternative that serves the equivalent purpose"), 1.3.1 Info and Relationships ("Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text"), 2.4.2 Page Titled, 2.4.6 Headings and Labels, 3.1.1 Language of Page ("The default human language of each web page can be programmatically determined"). A misnested `i` fails none of them; a heading that jumps from h1 to h3 fails 1.3.1 and the HTML Standard's own rule that each heading's level be "less than, equal to, or 1 greater than" the one before; an `img` with no `alt` fails 1.1.1 and the standard's "the alt attribute must be specified and its value must not be empty". The explainer's findings are these sentences, applied to the tree that actually came out.

## Sources

- [HTML Living Standard, 13.2.2 Parse errors](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors) (read 2026-10-05)
- [HTML Living Standard, 13.1.2 Elements: void elements, optional tags](https://html.spec.whatwg.org/multipage/syntax.html#optional-tags) (read 2026-10-05)
- [HTML Living Standard, 13.2.6 Tree construction](https://html.spec.whatwg.org/multipage/parsing.html#tree-construction) (read 2026-10-05)
- [HTML Living Standard, 4.3.11 Headings and outlines](https://html.spec.whatwg.org/multipage/sections.html#headings-and-outlines) (read 2026-10-05)
- [HTML Living Standard, 4.8.4.4 Requirements for providing text to act as an alternative for images](https://html.spec.whatwg.org/multipage/images.html#alt) (read 2026-10-05)
- [WCAG 2.1, W3C Recommendation 06 May 2025: Success Criterion 4.1.1 Parsing](https://www.w3.org/TR/WCAG21/#parsing) (read 2026-10-05)
- [WCAG 2.2, W3C Recommendation 12 December 2024](https://www.w3.org/TR/WCAG22/) (read 2026-10-05)
- [Understanding SC 4.1.1 Parsing (Obsolete and removed)](https://www.w3.org/WAI/WCAG22/Understanding/parsing.html) (read 2026-10-05)
