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.
How does someone who cannot see the layout find their way around this page?
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.
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.
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.
- 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
h1toh4. 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-reverseand 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
mainelement, 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
navelements are announced as "navigation, navigation, navigation". Landmarks without names are only marginally better than no landmarks. - Content hidden with
opacity: 0,clip-pathor 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.
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:
headerto banner,navto navigation,mainto main,asideto complementary,footerto contentinfo,formandsectionto their roles only when they have an accessible name. That last condition is the one people miss. - Headings
h1–h6are exposed as a flat, levelled list that assistive technology turns into a navigable outline. The old "document outline algorithm" that would have derived levels fromsectionnesting was never implemented by any browser and has been removed from the specification — levels are exactly the numbers you type. display: none, thehiddenattribute andinertremove content from the accessibility tree and from the focus order.visibility: hiddenremoves it from both as well. Opacity, clipping and off-screen positioning remove it from neither (Focus Management).langonhtmlselects the speech synthesiser's voice and pronunciation rules, and drives hyphenation and locale-correct quotation marks. Alangmismatch 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
mainwithtabindex="-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.
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.
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 link | Moves both scroll position and focus into main, because the target has tabindex="-1". |
| Screen-reader landmark commands | Jump between banner, navigation, main and contentinfo without reading the content between them. |
| Screen-reader heading commands | Walk the heading outline by level — the most-used navigation method there is. |
- — The skip link may be visually hidden until focused, but must never be removed from the focus order.
- —
mainneedstabindex="-1"so it can receive programmatic focus without joining the tab sequence. - — After a client-side navigation, move focus to the new
mainor 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).
- — 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.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
order: -1 or flex-direction: row-reverse at one breakpoint | Focus jumps backwards across the screen; screen-reader order does not match the design | CSS reordered the boxes; the DOM, the accessibility tree and the tab order were untouched | Reorder in the source and use grid template areas, which move content without changing item order. |
| Heading level chosen to match a type scale | The outline skips levels; users hear that something is missing | Level was treated as a size token rather than as document structure | Pick the level by containment and set the size with a class (Design Systems). |
A modal rendered without making the background inert | Screen-reader users read straight through the dialog into the page behind it | A visual focus trap constrains Tab, not the reading cursor | Use <dialog> with showModal(), or apply inert to the rest of the document (What Native Elements Already Do). |
A skip link that only sets location.hash | The page scrolls, then the next Tab returns the user to the top | Scroll moved; focus did not, because the target is not focusable | Add tabindex="-1" to the target so focus follows the anchor. |
Two layout components each rendering <main> | Inconsistent landmark navigation between browsers and screen readers | Nested layout ownership; both were locally correct | Own 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: none | Tab focus disappears into an invisible region | The content is still in the accessibility tree and still focusable | Hide 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
h1that 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 onemain. Name repeated landmarks witharia-labelso "navigation" becomes "primary navigation" and "breadcrumb". - Make a skip link the first focusable element, pointing at a
mainthat carriestabindex="-1"so focus actually lands there rather than only the scroll position moving. - Set
langonhtml, 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
hiddenorinert, 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
h1naming 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". langselects 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
- 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 adiv.- 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
tabindexvalues 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.
- 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
mainexists, it names a page that is not there yet (Live Regions and Announcement).
- 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
h1rewrites 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
mainregion rendered only for administrators is a rendering decision that the server must independently enforce (Where Authorization Must Live in Security Engineering).
- "
h1is the biggest text." Heading level is document structure; size is CSS. They have been separable since stylesheets existed. - "
aria-hiddenhides 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." Positivetabindexcreates 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.
- "
sectionis the semanticdiv." Without an accessible name it maps to a generic node, so it is adivthat looks like it did something.
Measuring it, and what changes in the field
- The full-page accessibility tree view in devtools shows what the structure actually resolved to, including every
sectionthat 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, h6shows 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
mainfails a test rather than a support ticket (Component Testing).
- On a mobile screen reader the linear swipe order is the only order, so a
order: -1that 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,
dirhandles direction correctly across text, layout and scrolling, while a hand-rolledrow-reversehandles 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).
- 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-labelon threenavelements 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
sectionnesting, 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 yousectionrenumbers your headings as out of date.
Where the depth lives
This domain teaches the browser-side mechanism and hands the rest off.
- — 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.