HTMLGENERALPLATFORM-SPECIFICSPEC-EVOLVING

Document Structure and Reading Order

Landmarks, headings and source order are the navigation system most of your users never see — and the one CSS can silently disagree with.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

How does someone who cannot see the layout find their way around this page?

The user intent

A person wants to get to the part they came for — the search box, the article, the error message — without being read everything that precedes it.

The obvious build

Structure is what the layout looks like. Put elements wherever the grid needs them, arrange the page with CSS, and pick heading levels by how big the text should be.

Why it breaks

Screen-reader users navigate by heading and by landmark far more than they read linearly. A div class="section-title" styled at 28px is not a heading, so it does not appear in that list and the section is unreachable by the fastest route.

How it breaks in a real browser
  • Screen-reader users navigate by heading and by landmark far more than they read linearly. A div class="section-title" styled at 28px is not a heading, so it does not appear in that list and the section is unreachable by the fastest route.
  • Heading levels chosen for size produce an outline that jumps from h1 to h4. The user hears the jump as "something was skipped" and cannot tell whether content is missing or the page is just built badly.
  • Flexbox order, row-reverse and grid placement change the visual order and not the DOM order. Focus then travels around the screen in a sequence that looks random, which is disorienting for a sighted keyboard user and invisible to everyone testing with a mouse.
  • With no main element, a skip link has nowhere to go, and the user has to tab through the entire navigation on every page load (Keyboard Operability).
  • Three unlabelled nav elements are announced as "navigation, navigation, navigation". Landmarks without names are only marginally better than no landmarks.
  • Content hidden with opacity: 0, clip-path or an off-screen transform is still in the accessibility tree and still focusable. A closed menu that is invisible and reachable is worse than one that is visible.
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • There are three orders in a page and only two of them are the same. DOM order determines the accessibility tree order and the sequential focus order; CSS determines the visual order. Nothing reconciles them for you (The DOM Is Not Your HTML).
  • Sectioning and landmark elements map to landmark roles: header to banner, nav to navigation, main to main, aside to complementary, footer to contentinfo, form and section to their roles only when they have an accessible name. That last condition is the one people miss.
  • Headings h1h6 are exposed as a flat, levelled list that assistive technology turns into a navigable outline. The old "document outline algorithm" that would have derived levels from section nesting was never implemented by any browser and has been removed from the specification — levels are exactly the numbers you type.
  • display: none, the hidden attribute and inert remove content from the accessibility tree and from the focus order. visibility: hidden removes it from both as well. Opacity, clipping and off-screen positioning remove it from neither (Focus Management).
  • lang on html selects the speech synthesiser's voice and pronunciation rules, and drives hyphenation and locale-correct quotation marks. A lang mismatch does not degrade gracefully — it renders speech unintelligible (Internationalization).
  • The document is a tree, and every consumer of it — the accessibility tree, the focus order, find-in-page, reader mode, a crawler — walks it in document order, which is depth-first pre-order (Depth-First Search (DFS) in Algorithms & Data Structures).

What this makes the browser do

And which of it is avoidable.

  • Building the accessibility tree alongside the DOM, computing an accessible name for every landmark and heading, and repairing the affected region whenever structure changes.
  • Reordering with CSS costs the layout engine nothing unusual and costs the accessibility tree nothing at all — which is precisely the problem. There is no work for the browser to do to make the two agree, so it does not do any.
  • Deep wrapper nesting increases the node count that style calculation, layout and the accessibility tree all scale with (Style Calculation).
  • The avoidable work is a skip link that is implemented in JavaScript by scrolling: a plain anchor to a main with tabindex="-1" moves focus and scroll with no script at all (Scroll Restoration).

Three orders, two of which must agree

This is the whole lesson in one picture. The DOM is the single source that the accessibility tree, the focus order, find-in-page and every crawler read. CSS produces a separate visual order, and the browser makes no attempt to reconcile the two — there is no warning, no error, and no devtools panel that flags the divergence.

The practical consequence is a rule rather than a technique: order the source the way a person should read it, and let CSS handle appearance. When you catch yourself reaching for order to fix meaning rather than presentation, the markup is what needs changing.

  • DOM order is not negotiable: it *is* the focus order and the announcement order.
  • CSS order is free to differ, and nothing tells you when it has.
  • A sighted keyboard user experiences both at once, which is why they notice a divergence that neither a mouse user nor a screen-reader user reports.
One tree, two consumers, and the order CSS invents
parseddocument orderdocument orderstyledmay reorderHTML source orderDOM tree (document order)Accessibility treeSequential focus orderFind-in-page, reader mode, crawlersCSS: flex order, grid areas, absolute positioningVisual order on screenWhat the user experiences
UserLLMAgentToolDataDecisionHumanGuardrail

The landmark set, as a specification

Landmarks are a small, fixed vocabulary, and the whole benefit comes from using them the way every other site does. Inventing your own regions is not additive — it is noise in the one list a user relies on to move quickly.

The skip link is included here deliberately. It is not an accessibility extra bolted onto the structure; it is the structure being usable. One anchor, one tabindex="-1" on the target, and the navigation stops costing a user forty keystrokes per page.

accessibility specDocument landmarksPage landmarks and the skip link

semantics header (banner), nav (navigation, named when repeated), main (exactly one, tabindex="-1"), aside (complementary), footer (contentinfo). section only counts as a region when it carries an accessible name.

Tab (first press)Reaches the skip link, which should be the first focusable element in the document and visible once focused.
Enter on the skip linkMoves both scroll position and focus into main, because the target has tabindex="-1".
Screen-reader landmark commandsJump between banner, navigation, main and contentinfo without reading the content between them.
Screen-reader heading commandsWalk the heading outline by level — the most-used navigation method there is.
Focus
  • The skip link may be visually hidden until focused, but must never be removed from the focus order.
  • main needs tabindex="-1" so it can receive programmatic focus without joining the tab sequence.
  • After a client-side navigation, move focus to the new main or to its heading — the browser does this for you on a document load and does nothing for you on a route change (Client-Side Routing).
Announces
  • The landmark role and its name on entry: "primary navigation, navigation".
  • The heading text and level, so structure is audible without reading the body.
  • The document title on load, which is why it must be unique per route (The Head: Metadata That Changes Rendering).

usually broken by Labelling everything. Once every wrapper is a named region, the landmark list is as long as the page and the mechanism that made navigation fast has been used to make it slow. Four to six landmarks on a page is a working structure; twenty is a symptom.

How structure gets broken in practice

None of these are exotic. Each one is a locally reasonable decision — a designer wanted the sidebar on the left at one breakpoint, a component library shipped a layout wrapper, a modal was built before inert was available — and each one is invisible to the person who made it.

The response column is the part worth memorising. In every row the fix is in the markup or in one attribute, not in a JavaScript workaround, which is a good general signal that the structure was the problem in the first place.

Structure defects and where they actually come from
TriggerSymptomCauseResponse
order: -1 or flex-direction: row-reverse at one breakpointFocus jumps backwards across the screen; screen-reader order does not match the designCSS reordered the boxes; the DOM, the accessibility tree and the tab order were untouchedReorder in the source and use grid template areas, which move content without changing item order.
Heading level chosen to match a type scaleThe outline skips levels; users hear that something is missingLevel was treated as a size token rather than as document structurePick the level by containment and set the size with a class (Design Systems).
A modal rendered without making the background inertScreen-reader users read straight through the dialog into the page behind itA visual focus trap constrains Tab, not the reading cursorUse <dialog> with showModal(), or apply inert to the rest of the document (What Native Elements Already Do).
A skip link that only sets location.hashThe page scrolls, then the next Tab returns the user to the topScroll moved; focus did not, because the target is not focusableAdd tabindex="-1" to the target so focus follows the anchor.
Two layout components each rendering <main>Inconsistent landmark navigation between browsers and screen readersNested layout ownership; both were locally correctOwn the landmark set in one layout component and forbid it below that boundary (Drawing Component Boundaries).
A menu hidden with opacity: 0 and pointer-events: noneTab focus disappears into an invisible regionThe content is still in the accessibility tree and still focusableHide with hidden, display: none or inert — visual hiding is not hiding.

How to build it

Most important first.

  • Write source order as reading order, then adapt with CSS. Treat any visual reorder that changes *meaning* rather than *presentation* as a bug in the markup, not a feature of the layout.
  • Use one h1 that names the page, and choose every subsequent level by containment, never by size. Font size is a CSS decision and has no relationship to the outline.
  • Give the page the standard landmark set — header, nav, main, footer — with exactly one main. Name repeated landmarks with aria-label so "navigation" becomes "primary navigation" and "breadcrumb".
  • Make a skip link the first focusable element, pointing at a main that carries tabindex="-1" so focus actually lands there rather than only the scroll position moving.
  • Set lang on html, and on any element whose language differs from the page. This is the cheapest correctness fix in the domain and the most consistently omitted.
  • Hide things with hidden or inert, not with visual tricks. If text is meant only for assistive technology, use a visually-hidden utility class that keeps it in the tree, and use it sparingly (The Rules of ARIA).

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • Landmarks and headings are the primary navigation for screen-reader users. Surveys of screen-reader users have consistently put headings at the top of the list of ways they find content on a page — and the same structure serves users of magnification, switch access and voice control.
  • DOM order is the focus order. Any layout technique that changes visual order changes the relationship between where focus appears to be and where it is next going.
  • One h1 naming the page, then a level structure that follows containment, lets a user build a mental map of a page they have never seen without reading any of it.
  • A skip link is the difference between four Tab presses and forty on every page of a site. It is one anchor and one tabindex="-1".
  • lang selects the voice. A French page announced by an English synthesiser is not slightly worse; it is unusable.
  • On mobile screen readers, navigation is a strictly linear swipe through the accessibility tree, so a visual reorder is felt more sharply there than on the desktop where a rotor offers a way around it.

What can go wrong

Failure modes
  • A skip link that moves the scroll position but not focus, because the target has no tabindex="-1". The next Tab press returns the user to the top of the navigation.
  • <section> used as a generic wrapper. Without an accessible name it maps to a generic node, so it is simultaneously more semantic-looking and no more useful than a div.
  • More than one main, usually from a layout component rendered inside a route component that also renders one. Assistive technology behaviour here is undefined and inconsistent.
  • A modal that does not make the rest of the page inert. The visual focus trap holds, and a screen-reader user reads straight past the dialog into the content behind it (Focus Management).
  • aria-hidden="true" on a container that holds a focusable element. The element remains reachable by Tab and is invisible to the accessibility tree, so focus vanishes into nothing.
  • Positive tabindex values used to "fix" an order that CSS broke. They lift the element above every natural tab stop on the page and turn one wrong order into two.
  • Headings generated from a design token map, so a visual redesign silently changes the document outline.
What can arrive out of order
  • Content that arrives after the user has already moved: a skip link takes focus to main, and an async data load then replaces the subtree that focus was inside, dropping focus back to the document body with no announcement (Route Loading Boundaries).
  • A client-side route change updates the DOM structure and the title at different moments. If the announcement fires before the new main exists, it names a page that is not there yet (Live Regions and Announcement).
Security
  • Structure is not a control. Content rendered and then hidden with CSS is fully present in the response, readable in view-source, and available to anyone who opens devtools (Authorization-Aware UI).
  • User-supplied content that is allowed to contain markup can inject headings and landmarks, restructuring the page for assistive technology even when it looks harmless — a comment containing an h1 rewrites the outline of the article (Sanitization and Trusted HTML).
  • Visual position and event target are different things. An overlay, an iframe or a transformed element can put what a user believes they are clicking somewhere other than where the click lands (Clickjacking and Framing).
  • Nothing in the structure authenticates anything. A main region rendered only for administrators is a rendering decision that the server must independently enforce (Where Authorization Must Live in Security Engineering).
Misreads
  • "h1 is the biggest text." Heading level is document structure; size is CSS. They have been separable since stylesheets existed.
  • "aria-hidden hides it." It hides it from assistive technology only. It remains visible, focusable and clickable, which is the exact combination that strands a keyboard user.
  • "A wrong tab order can be fixed with tabindex." Positive tabindex creates a second, higher-priority tab sequence that runs before every other element on the page. The fix is the DOM order.
  • "Screen readers read the page the way it looks." They read the accessibility tree, in DOM order. The visual layout is not an input to that process at all.
  • "section is the semantic div." Without an accessible name it maps to a generic node, so it is a div that looks like it did something.

Measuring it, and what changes in the field

How you would see this
  • The full-page accessibility tree view in devtools shows what the structure actually resolved to, including every section that turned out to be generic (A Mental Model of the Devtools).
  • A heading-outline extension or a one-line query for h1, h2, h3, h4, h5, h6 shows level jumps in seconds.
  • Tab from the address bar to the end of the page and watch where the indicator goes. If it moves backwards visually, source order and visual order have diverged.
  • Tests that query by role and by accessible name make the landmark set part of the contract, so removing a main fails a test rather than a support ticket (Component Testing).
Slow device, slow network, large data, old tab
  • On a mobile screen reader the linear swipe order is the only order, so a order: -1 that is merely confusing on the desktop becomes a genuine dead end.
  • On a long page or an infinite feed, landmarks and headings are the only way to get anywhere. Structure matters in proportion to content volume (List Virtualization).
  • In right-to-left locales, dir handles direction correctly across text, layout and scrolling, while a hand-rolled row-reverse handles only the boxes (Internationalization).
  • In a client-side-routed application, the whole structure is replaced without a document load, so nothing announces the new page unless you arrange it (Client-Side Routing).
What this costs
  • Source-order-first constrains layout. Occasionally the design genuinely wants a different order at one breakpoint, and the honest fixes are a wrapper, a grid template that changes areas rather than item order, or accepting the visual compromise.
  • Rendering the same content twice for two layouts doubles the maintenance cost, doubles the DOM, and risks announcing it twice unless one copy is properly hidden — usually a worse trade than it appears.
  • Landmark and label discipline is verbose. aria-label on three nav elements is three attributes that must stay accurate through every redesign, and a stale label is worse than none.

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALLandmark mapping, the flat heading model and the rule that DOM order drives both the accessibility tree and the sequential focus order are specified behaviour and consistent across Blink, Gecko and WebKit.
  • PLATFORM-SPECIFICHow landmarks are surfaced is an assistive-technology decision: NVDA offers a landmark list and a D-key jump, VoiceOver exposes them through the rotor, and TalkBack surfaces headings more prominently than landmarks. The structure is the contract; the navigation gesture is not yours to design.
  • SPEC-EVOLVINGThe HTML outline algorithm, which would have derived heading levels from section nesting, was specified for years, implemented by nobody, and has now been removed. Advice written against it is still widely circulated — treat any source that tells you section renumbers your headings as out of date.

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

Domains that do not exist yet
  • Software Design — the same argument at a different scale: a structure that is easy to navigate is one whose containment relationships were decided once, at a boundary, rather than emerging from whichever component rendered first.