Web Platform · HTML and Semantics

Optional Tags and the Implied End Tag Rules the Parser Enforces

Only some tags may be omitted, and only where the tree builder already closes the element. The conformance conditions behind each omission, and the cases where leaving a tag out changes the tree.

<ul><li>one<li>two</ul> is valid markup and produces two list items. The second <li> closes the first; the </ul> closes the second. Nothing is inferred about the author’s intent, because the tree builder has a rule that a start tag for li closes an open li. The same rule set permits the end tags of p, td, tr, thead, tbody, option, dt, and dd to be left out in specific situations, and permits the start tags of html, head, and body to be left out altogether.

The distinction that matters is between an element and its tags. Omitting tags never omits the element. The li element exists in the DOM, takes styles, appears in children, and is written back out by any serializer. What changes is the source text, and the only risk is that the omitted tag was carrying information the parser needed in order to close the element at the right place.

Two separate rule sets

The HTML Standard describes tag omission twice, in two different chapters with two different purposes.

The syntax chapter states, per element, the conditions under which its start or end tag may be omitted. These are conformance requirements; a validator checks them. The parsing chapter contains the tree-construction rules that actually close elements, and they are what the browser applies. The two do not coincide exactly, and the syntax rules are written to describe only omissions that the parser already handles deterministically.

The set of elements with an omissible end tag is small and closed: li, dt, dd, p, rt, rp, optgroup, option, colgroup, caption, thead, tbody, tfoot, tr, td, th, plus html, head, and body. Elements outside that list — div, span, a, script, textarea, title, select, table, form, video — require their end tags. A missing </div> does not produce an error the author sees; the element is closed where the enclosing structure ends, and that point is often not the one intended.

Generate implied end tags

The tree builder carries a named operation called generating implied end tags. It pops elements from the stack of open elements while the current node is one of dd, dt, li, optgroup, option, p, rb, rp, rt, or rtc, and stops at the first node that is none of those. Rules that close a list item or a paragraph invoke it, so a chain of open li elements collapses in one step rather than requiring a matching end tag for each.

A separate rule list governs paragraphs. When a start tag for address, article, aside, blockquote, details, div, dl, fieldset, figcaption, figure, footer, form, h1 through h6, header, hgroup, hr, main, menu, nav, ol, p, pre, section, table, or ul arrives while a p element is open in button scope, the parser closes the paragraph first. That behaviour is why <p>text<div>block</div> yields a paragraph containing the text followed by the div, and nothing else: the div start tag closes the paragraph. It is also why <p><div></div></p> produces three nodes: the opening p is closed by the div start tag, and the stray </p> at the end creates a second, empty paragraph.

Table structure has its own implied elements. A tr start tag with no enclosing section causes the parser to insert a tbody, so the tree contains an element the source never mentioned. A td or th start tag closes an open cell before opening the new one, which is why </td> is usually unnecessary. A thead or tfoot start tag closes an open tbody. All of this is covered in more detail in The Table Model, Foster Parenting, and Header Association.

Where the conformance rules are narrower than the parser

The parser will close a paragraph before any of the block elements above. The syntax chapter allows the </p> end tag to be omitted only if the paragraph is followed by one of those elements, or if there is no more content in the parent and the parent is not one of a, audio, del, ins, map, noscript, or video, nor an autonomous custom element. The excluded parents are the transparent and media containers, whose own content model depends on where they are placed.

The html, head, and body start tags have their own conditions. <html> may be omitted when the first thing in the document is not a comment. <head> may be omitted when the element is empty, or when the first thing inside it is an element. <body> may be omitted when the element is empty, or when the first thing inside it is not whitespace or a comment, except that a leading meta, link, script, style, or template element requires the start tag to be present. Those exceptions all follow from the same cause: omitting the tag would change which element the following token is attached to.

A comment is the recurring hazard. <body> cannot be omitted before a comment because the comment would be attached before the body, or the body would start after it. Similarly, a tbody start tag may be omitted only when the first thing inside it is a tr, the element is not empty, and it is not immediately preceded by a tbody, thead, or tfoot element whose end tag was also omitted. The rules are written as guards against ambiguity, not as general permission.

Omission is not a separator

Nesting rules are not repaired by omission. Two anchors do not nest: a start tag for a while an a element is open in the active formatting list causes the parser to close the existing anchor, so the two links become siblings rather than nested elements. Two buttons do not nest either; a button start tag while a button is in scope closes the open one. A form start tag inside an open form is ignored outright, leaving the inner controls attached to the outer form.

These are exactly the cases where a tool that drops end tags can produce a tree it did not intend. In each case the parser repairs by closing, so the result is a flat structure at the point of the duplicate, and the loss shows up as a missing nested element rather than as a syntax error. The parser-level details are in How the Tokenizer and Tree Builder Recover from Malformed Markup.

What the omission buys

The byte saving is small and easy to overestimate. A </li> is five bytes, a </p> is four, a </td> is five. Brotli and gzip turn repeated closing tags into short back-references, so the compressed saving per element is small. The uncompressed saving matters only for documents streamed without compression, which is rare.

The parser saving is zero in the useful sense. The browser synthesizes the same elements whether or not the tags were written, and any difference in tree-construction work is too small to matter. There is no measurable render difference, because the DOM is the same.

What does change is the behaviour of tools that treat markup as text rather than as a tree. A diff of two documents with different omission styles shows changed lines even when the trees are identical. An editor that folds by matching tags has nothing to fold. A template language that wraps fragments in an element can inadvertently move the implied closure point, so a <p> that was closed by the </div> written later now ends somewhere else. For a tool that emits markup consumed by other tools, full end tags everywhere except li, p, td, tr, and option avoid the ambiguity at no meaningful cost.

Choosing omissions that cannot change the tree

Four omissions are safe under almost any editing: </li> before the next li or before </ul> or </ol>; </p> before one of the block start tags listed above; </td> and </th> before the next cell or before </tr>; </tr> before the next row or before the end of the section. Two more are safe for tool-emitted markup: </option> before another option or before </select>, and </dt> or </dd> before the other term or definition.

The ones to avoid are those whose closure depends on a following sibling the tool does not control. Omitting </p> inside <a> makes validity depend on what comes after the anchor. Omitting </tbody> is legal only when the next thing is another tbody or a tfoot, or when there is no more content in the table, and the condition is stated in terms of omitted tags around it, which is easy to get wrong when fragments are composed. For anything assembled from separately produced pieces, writing the end tag is cheaper than reasoning about the context.

Serialization hides all of this. element.outerHTML writes end tags for every element that requires them and for many that do not, so the source that produced the tree and the source that reproduces it from the tree are different texts. Any tool chain that compares them, or that stores serialized HTML and later parses it again, is comparing a normalized form against an original one; expecting equality there is a category error rather than a bug in the serializer.

References

  1. HTML Standard — Optional tags — WHATWG
  2. HTML Standard — Tree construction — WHATWG
  3. HTML Standard — HTML serialization methods — WHATWG