Reading order & meaningful sequence

A page has two orders, and they are supposed to be the same one. There is the order the markup is written in — the DOM order — and there is the order a person actually experiences the content. For a screen reader reading top to bottom, and for a keyboard user pressing Tab, that experienced order is the DOM order. Sighted mouse users, by contrast, see whatever order CSS paints on screen. When those two drift apart, the meaning drifts with them: a “Buy” button that visually follows a price can be read before it, instructions can arrive after the step they describe, and focus can leap around the screen in a sequence no one designed.

The fix is almost always the same principle: write the DOM in the order the content makes sense to read, then let CSS and ARIA follow that order rather than fight it. This lesson works through the three ways the two orders most often separate — visual reordering with flexbox or grid, focus order hijacked by a positive tabindex, and content hidden from the wrong audience — and shows how to keep the sequence meaningful for everyone.

What you’ll learn

Why DOM order is the reading and focus order for assistive technology; how the CSS order property and grid placement can divorce the visual sequence from the source; why a positive tabindex breaks the natural tab order for the whole page; and how to choose between display:none, the hidden attribute, a visually-hidden class, and aria-hidden so content reaches exactly the audience it should.

Standards this lesson maps to
Standard Criterion Level What it requires
WCAG 2.2 1.3.2 Meaningful Sequence A When the order of content affects its meaning, that correct reading sequence can be determined programmatically (from the DOM).
WCAG 2.2 2.4.3 Focus Order A Components receive focus in an order that preserves meaning and operability.
WCAG 2.2 1.3.1 Info and Relationships A Structure and relationships conveyed visually are also available in the markup, including what is and isn’t exposed to assistive technology.
EN 301 549 9.1.3.2 / 9.2.4.3 (incorporates WCAG) European harmonised standard; references the WCAG A/AA set including sequence and focus-order criteria.
Section 508 502.3 / 504 (incorporates WCAG A & AA) US federal ICT must meet WCAG 2.0 Level A and AA, including meaningful sequence and focus order.
ADA Title II WCAG 2.1 AA (DOJ rule) AA US state/local government web content must conform to WCAG 2.1 AA.

The three ways order breaks

Each card below isolates one common way the visual, reading, and focus orders fall out of step. For every issue you get a plain-language statement of the problem, a Bad example (shown as escaped, non-running code so it can’t harm this page), a Good example, the copyable Code, and an ordered fix.

CSS reorders content away from the DOM

WCAG 2.2 · 1.3.2 A EN 301 549 Section 508 ADA Title II

Flexbox and grid let you place a box anywhere on screen regardless of where it sits in the markup — with the order property, row-reverse/column-reverse, or explicit grid-row/grid-column placement. None of that changes the DOM. A screen reader still reads, and the Tab key still moves, in source order. So if the source says “price, then description, then Buy” but CSS paints “Buy, then price”, a sighted user and a screen reader user are handed two different stories. When the sequence carries meaning — steps, a question before its options, a control after the thing it acts on — that mismatch is a 1.3.2 failure.

Bad

The markup lists the actions before the warning, then CSS visually flips them so the warning appears on top. A sighted user reads “Warning, then Delete”; a screen reader reads “Delete, then Warning”. The order changes the meaning.

bad-css-order.html
<div style="display:flex; flex-direction:column">
  <button>Delete account</button>          <!-- DOM 1st -->
  <p style="order:-1">This cannot be undone.</p> <!-- painted 1st -->
</div>
<!-- Eyes: warning then button. Screen reader: button then warning. -->

Good

The DOM is written in the order the content should be read — warning first, then the action. No order override is needed, so the visual, reading, and focus orders all agree.

good-dom-order.html
<div style="display:flex; flex-direction:column">
  <p>This cannot be undone.</p>
  <button>Delete account</button>
</div>
<!-- One order for everyone. -->

Code

order is fine for purely presentational shuffling that does not change meaning — for example reflowing equal sibling cards at a breakpoint. The test: read the DOM aloud. If it still makes sense, the visual reorder is safe. If it doesn’t, change the DOM, not the CSS.

order-presentational.css
/* Safe: equal, independent cards rearranged for layout only. */
@media (min-width: 60rem) {
  .feature--promoted { order: -1; } /* still reads fine in any order */
}

/* Risky: never use order to move a control away from the
   content it relates to, or a step out of its sequence. */

How to fix

  1. Write the DOM in the order the content should be read and operated, before you reach for layout CSS.
  2. Read the source order aloud (or run the page through a screen reader). If the meaning holds, you’re fine; if it doesn’t, reorder the markup.
  3. Limit order, *-reverse, and manual grid placement to rearrangements that don’t change meaning relative to neighbouring content.
  4. Re-check after responsive breakpoints — a layout that reads correctly on desktop can scramble on mobile if order is doing the work.

A positive tabindex overrides the natural focus order

WCAG 2.2 · 2.4.3 A EN 301 549 Section 508 ADA Title II

By default the browser walks focusable elements in DOM order — a tab order you mostly get for free. Any tabindex greater than zero opts out of that. The browser first visits every element with a positive value, in ascending number order, and only then falls back to DOM order for the rest. So a single tabindex="1" on one button can yank focus to it ahead of everything above it, and a handful of stray positive values produce a focus path that jumps around the page unpredictably. It’s brittle, too: insert a new field and the hand-numbered sequence is wrong again. This breaks 2.4.3 because the focus order no longer preserves meaning or operability.

Bad

Hand-numbered tab values try to force an order. Because positive values are visited first, the “Submit” button at tabindex="1" is reached before the name and email fields above it — focus jumps to the bottom, then back up.

bad-tabindex.html
<input id="name" tabindex="3">
<input id="email" tabindex="2">
<button tabindex="1">Submit</button>
<!-- Tab order: Submit → email → name. Backwards. -->

Good

Remove the positive values and order the DOM correctly. Native controls are focusable already, so with no tabindex at all the tab order simply follows the source: name, email, Submit.

good-tabindex.html
<input id="name">
<input id="email">
<button>Submit</button>
<!-- Tab order follows the DOM: name → email → Submit. -->

Code

The only valid tabindex values are 0 — add a custom element to the natural tab order — and -1 — make an element focusable by script (e.g. to move focus to a dialog or error summary) without putting it in the tab sequence. Never a positive number.

tabindex-values.html
<!-- 0: custom control joins the tab order in DOM position -->
<div role="button" tabindex="0">Toggle</div>

<!-- -1: focusable by script only, not via Tab -->
<div role="dialog" tabindex="-1" id="dialog">…</div>
<!-- dialog.focus() when it opens -->

<!-- Never: tabindex="1" or any positive value -->

How to fix

  1. Search the codebase for tabindex with a positive value and remove every one.
  2. Fix focus order by reordering the DOM so the source matches the intended sequence — that’s what the browser follows.
  3. Use tabindex="0" only to make a genuinely custom interactive element focusable, and give it the right role and keyboard handlers.
  4. Use tabindex="-1" for script-managed focus (dialogs, error summaries, “skip” targets), never to reorder.
  5. Tab through the whole page and confirm focus moves in a logical, predictable path with no jumps.

Hiding content from the wrong audience

WCAG 2.2 · 1.3.1 A EN 301 549 Section 508 ADA Title II

Hiding is part of reading order: anything removed from the accessibility tree simply isn’t in the screen reader’s sequence. The four common techniques hide from different audiences, and mixing them up either leaks content that should be gone or strips content that should stay. display:none and the hidden attribute remove an element from everyone — layout and the accessibility tree alike. A visually-hidden (or “sr-only”) class hides it visually but keeps it for screen readers — ideal for a label or skip link. aria-hidden="true" does the opposite: it stays on screen but vanishes from assistive tech. The two classic mistakes are using display:none on text that screen reader users need, and putting aria-hidden="true" on (or around) something focusable — which leaves keyboard users tabbing to a control that announces nothing.

Bad

The visible “×” glyph is the only label on the close button, and it’s marked aria-hidden. The button is still in the tab order but is now nameless — a screen reader announces “button” with nothing more.

bad-hidden.html
<!-- A: label hidden from screen readers, button left nameless -->
<button aria-hidden="true">&times;</button>

<!-- B: instruction removed from everyone, but SR users need it -->
<p style="display:none">Format: DD/MM/YYYY</p>

Good

The decorative glyph is hidden from assistive tech while a real accessible name is supplied, and the format hint is hidden visually but kept for screen readers with a utility class.

good-hidden.html
<!-- A: glyph decorative, real name on the button -->
<button aria-label="Close"><span aria-hidden="true">&times;</span></button>

<!-- B: hint kept for screen readers, hidden from sight -->
<p class="visually-hidden">Format: DD/MM/YYYY</p>

Code

This is the visually-hidden utility: off-screen for sighted users, fully present in the accessibility tree. It must not use display:none or visibility:hidden, which would remove it from screen readers too.

visually-hidden.css
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

How to fix

  1. Decide who should not see each piece of content, then pick the matching technique on purpose.
  2. Use display:none or the hidden attribute to remove content from everyone — never for text screen reader users still need.
  3. Use a visually-hidden class to keep content for assistive tech while hiding it visually (labels, hints, skip links).
  4. Use aria-hidden="true" only on decorative or duplicate content, and never on a focusable element or one that contains a focusable element.
  5. When you hide a focusable control with CSS, make sure it’s removed from the tab order too, so focus never lands on something invisible.

Recap

  • Write the DOM in the order the content should be read. For assistive technology and keyboard users, source order is reading and focus order (1.3.2).
  • Treat CSS order, flex-direction: *-reverse, and grid placement as visual-only: never use them to move content to a position that changes its meaning relative to the source (1.3.2, 2.4.3).
  • Never use a positive tabindex. Use tabindex="0" to make something focusable and tabindex="-1" for programmatic focus only; fix order by reordering the DOM (2.4.3).
  • Hide with intent: display:none/hidden removes content from everyone, a visually-hidden class keeps it for screen readers, and aria-hidden="true" hides only from assistive tech — never on a focusable element (1.3.1).

One DOM that reads correctly satisfies WCAG, EN 301 549, Section 508, and ADA Title II at once — order the source meaningfully and the rest follows.