Semantic Foundations: HTML5, Document Engineering, and Accessibility as Architecture
Chapter 1: Semantic Foundations: HTML5, Document Engineering, and Accessibility as Architecture
Most engineering failures in modern frontends are diagnosed at the wrong layer. A team reports that screen readers announce a tabbed interface incorrectly, that keyboard users cannot trigger a primary action, that automated testing flags an unlabeled control, or that search engines fail to index a content page's hierarchy. Engineers then reach for the layer they understand — CSS, JavaScript, a framework, a build tool — and the fix fails, or fixes one symptom while creating two more. The root cause almost always sits one layer below: the HTML is structurally and semantically wrong, and downstream systems are merely reporting the consequences. This chapter argues that HTML is best understood as a contract layer that establishes meaning, ownership, and behavior contracts between the document and every system that consumes it, and that treating HTML as a visual substrate is the single most expensive misunderstanding in modern web engineering.
The argument matters because every downstream system reads HTML through its own lens. Screen readers walk the document tree and announce landmarks, headings, and controls in the order they appear. Search crawlers extract structure to compute topic hierarchies and ranking signals. Automated testing tools walk the DOM and infer the role of every interactive element to flag misconfigurations. Form-submission semantics, copy-paste, focus traversal, autofill, and the browser's built-in validation API all depend on the same primitive: elements that declare what they are and what they contain. When the contract is wrong, each of these systems produces a different, internally consistent, externally broken result. When the contract is correct, the same document satisfies all of them with no additional code.
The remainder of this chapter rebuilds that argument from first principles. We start with the HTML5 content-modeling logic that defines what elements mean and what they may contain. We separate the four trees every browser builds from one document — DOM, render, accessibility, and visual — and explain why the accessibility tree is the single source of truth that visual rendering must respect. We then refactor a real interactive primitive and walk through three custom widget patterns. We close with the specific WCAG 2.2 success criteria that map directly to engineering decisions, and the axe-core rules that should ship in every CI pipeline.
1.1 The Document Object Model as a Semantic Tree
HTML is, at the lowest level, a tree of nodes governed by a content model. Every element belongs to one or more *content categories* — metadata, flow, sectioning, heading, phrasing, embedded, interactive, palpable, script-supporting, form-associated — and the rules of those categories determine what that element may contain and what context it may live in. Understanding those categories is what separates an engineer who writes markup from an engineer who engineers a document.
Consider a `<section>` element. It belongs to flow content, sectioning content, and palpable content. That triple classification is doing real work. Flow content means it can appear anywhere in the body where a normal block of material is appropriate. Sectioning content means it creates a new section in the document with its own heading rank and contributes a descendant outline. Palpable content means it renders something — it is not purely structural noise. Now consider a `<nav>` element, which is flow, sectioning, and palpable, with an additional ARIA-implied role of `navigation`. The combination means a `<nav>` is not just a styled `<div>`; it is a flow-level section that browsers and assistive technologies will treat as a navigational landmark, contributing a specific entry in the landmarks list and exposing itself to skip-link mechanisms.
The sectioning content category — `<article>`, `<aside>`, `<nav>`, `<section>`, `<address>`, plus the sectioning roots `<blockquote>`, `<body>`, `<details>`, `<fieldset>`, `<figure>`, `<td>` — is where engineers most often get the contract wrong. The most common mistake is treating `<section>` as a generic wrapper, which is what `<div>` is for. The specification is explicit that `<section>` should be used only when the element's content has a heading that identifies it as a thematic grouping. A `<section>` without a heading is, in many practical respects, a misuse. `<article>` is more constrained: it represents a self-contained composition that could be syndicated, distributed, or reused independently. A product card on an e-commerce listing page is a better `<article>` than the wrapper that contains a grid of cards; that outer wrapper is typically a `<ul>` with role `list`, or, when each card is a navigation entry, a `<nav>` containing a `<ul>`.
The history of the outline algorithm matters here even though the algorithm itself was abandoned. In 2011 the HTML5 specification described a heading-based outline where `<h1>`–`<h6>` ranks would be computed from sectioning elements rather than from heading-level numbers. A `<section>` would demote its `<h1>` to an `<h2>`, and an `<article>` inside a `<section>` would demote further. Browsers never implemented this; assistive technologies never adopted it; the proposal remained in the spec for years with a permanent non-normative warning. The algorithm was removed from the WHATWG spec in 2014 and dropped from W3C HTML5.1 in 2017. The remaining model is the simpler, older one: heading ranks are determined by `<h1>`–`<h6>` numbers, and sectioning elements serve only as navigational landmarks and structural hooks. Any engineer who has inherited a codebase where headlines jump from `<h2>` to `<h4>` with no `<h3>` in sight, justified by "we use `<section>` so the outline handles it," is looking at code written against a non-existent algorithm. The fix is to use explicit heading levels in source order and let sectioning elements do their actual, more limited job.
That sectioning role is still valuable. Browsers map the sectioning content elements to ARIA landmark roles automatically: `<nav>` becomes `role="navigation"`, `<main>` (which is technically sectioning but treated separately because the HTML spec forbids more than one visible `<main>`) becomes `role="main"`, `<aside>` becomes `role="complementary"`, and `<footer>` becomes `role="contentinfo"` when it is a descendant of `<body>` and not nested inside a `<section>` or `<article>`. The mapping means that a screen reader's "navigate by landmark" command — pressing `M` in NVDA, `D` in JAWS, or using VoiceOver's rotor — exposes those elements directly, and a user can leap from one to the next without having to read linear content. Skipping the landmarks layer, even when the visual design is pixel-perfect, leaves users with cognitive or visual disabilities reading every page linearly.
Two further categories deserve attention because they govern nearly every interactive system an engineer will build. Phrasing content — the long list of inline elements from `<span>` to `<strong>` to `<a>` to `<button>` — defines what may appear inside a paragraph and what may not. A block-level element inside a `<p>` is invalid HTML, and the parser will silently restructure the document to close the `<p>` before the block. Interactive content — `<a>`, `<button>`, `<details>`, `<embed>`, `<iframe>`, `<label>`, `<select>`, `<textarea>`, `<audio>` (with controls), `<img>` (with `usemap`), `<input>` (not hidden), `<video>` (with controls) — describes what is keyboard-focusable by default, what is actionable, and what the browser considers a "control" for purposes of focus traversal, form participation, and accessibility tree role assignment. Interactive content cannot contain other interactive content. You cannot nest a `<button>` inside an `<a>`, nor a `<button>` inside a `<label>`. The parser will again restructure. The reason this matters architect