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.2AEN 301 549Section 508ADA 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
Write the DOM in the order the content should be read and operated, before
you reach for layout CSS.
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.
Limit order, *-reverse, and manual grid placement
to rearrangements that don’t change meaning relative to neighbouring content.
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.3AEN 301 549Section 508ADA 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.
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
Search the codebase for tabindex with a positive value and
remove every one.
Fix focus order by reordering the DOM so the source matches the intended
sequence — that’s what the browser follows.
Use tabindex="0" only to make a genuinely custom interactive
element focusable, and give it the right role and keyboard handlers.
Use tabindex="-1" for script-managed focus (dialogs, error
summaries, “skip” targets), never to reorder.
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.1AEN 301 549Section 508ADA 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">×</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">×</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.
Decide who should not see each piece of content, then pick the
matching technique on purpose.
Use display:none or the hidden attribute to remove
content from everyone — never for text screen reader users still need.
Use a visually-hidden class to keep content for assistive tech
while hiding it visually (labels, hints, skip links).
Use aria-hidden="true" only on decorative or duplicate content,
and never on a focusable element or one that contains a focusable element.
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.