Tech · · 6 min read
When the tab order stops matching what you see
reading-flow tells the browser to follow the visual order for focus and screen readers instead of the DOM. The seven values, and the thing it deliberately does not fix.
Move three flex items around with order, or place grid items into named areas, and the page looks right while the keyboard walks through it in a different sequence. reading-flow closes that gap:
.toolbar {
display: flex;
flex-direction: row-reverse;
reading-flow: flex-visual;
}With that declaration, sequential navigation and screen-reader order follow what a sighted reader sees — left to right in a left-to-right document — instead of the DOM.
It ships in Chromium only. At the time of writing, MDN's compatibility data records reading-flow and reading-order in Chrome 137 and later, with false for Firefox and Safari, so this is not yet a fix you can rely on everywhere. It is also not the fix for most source-order problems, which is the part worth reading before you use it.
Why order created this problem
CSS has been explicit about this for years. From CSS Display Module Level 4: the order property "does not affect the default traversal order of sequential navigation modes", and, as an advisement, "Authors must use order only for spatial, not logical, reordering of content. Style sheets that use order to perform logical reordering are non-conforming."
That rule exists so that non-visual presentations can trust the source order. It also leaves a genuine gap: a component whose visual arrangement legitimately differs per breakpoint has only one DOM order to offer, and one of those layouts will read wrong. reading-flow and reading-order exist for that case.
The seven values
drafts.csswg.org ↗CSS Display Module Level 4: the reading-flow propertyThe normative definition, including every value and the advisement about source order. Source: CSS Working Groupreading-flow applies to block, flex and grid containers. Any value other than normal makes the element a reading flow container.
| Value | Takes effect on | What the order becomes |
|---|---|---|
normal | — | DOM order. The initial value. |
source-order | block, flex, grid | DOM order, but reading-order on the children is honoured |
flex-visual | flex only | The visual order of the flex items, accounting for writing mode |
flex-flow | flex only | The flex-flow direction |
grid-rows | grid only | Visual order of grid items, row by row |
grid-columns | grid only | Visual order of grid items, column by column |
grid-order | grid only | Order-modified document order — identical to normal unless order is used |
Two consequences of the "takes effect on" column are easy to miss. flex-visual on a grid container does nothing at all — it is not an error, it simply has no effect. And when you use any flex-* or grid-* value, the order property is taken into account, which is usually the whole point.
Here is the spec's grid example. Four items are placed by area so that they appear in the order 4, 2, 3, 1:
<div class="wrapper">
<a class="a" href="#">Item 1</a>
<a class="b" href="#">Item 2</a>
<a class="c" href="#">Item 3</a>
<a class="d" href="#">Item 4</a>
</div>.wrapper {
display: grid;
grid-template-columns: repeat(3, 150px);
grid-template-areas: "d b b"
"c c a";
reading-flow: grid-rows;
}
.a { grid-area: a; }
.b { grid-area: b; }
.c { grid-area: c; }
.d { grid-area: d; }The reading order becomes "Item 4", "Item 2", "Item 3", "Item 1" — the order they appear on screen.
reading-order, for the one item that is out of place
Whole-container keywords do not cover every case. reading-order takes an <integer> on a direct child of a reading flow container and moves that item within the flow:
.wrapper { reading-flow: grid-rows; }
.top { reading-order: -1; }Lower ordinal groups are visited first, and the initial value is 0, so -1 pulls an item to the front without touching anything else. Where two items land in the same ordinal group, reading-flow breaks the tie.
This is also the reason source-order exists as a value. On its own it changes nothing about the order — it exists to turn the parent into a reading flow container so that reading-order on the children starts working:
.wrapper { reading-flow: source-order; }
.wrapper a:nth-child(3) { reading-order: -1; }What it deliberately does not do
Three limits, all in the spec.
It does not move anything. reading-flow "affects neither layout nor painting order and therefore has no effect on rendering to the visual canvas". It changes focus order and what is read aloud. Nothing else.
It is not a licence to write your markup in any order. The spec's advisement is blunt: "The source document should express the underlying logical order of elements." reading-flow and reading-order are "for cases where a given document can have multiple reading orders depending on layout changes, e.g. in response to media queries", and even then, "the most common or most fundamental reading order should be encoded in the source order so that the document is sensical without CSS". If your markup is scrambled at every breakpoint, the fix is the markup.
It only reaches direct children. reading-order applies to direct block-level, grid item or flex item children of a reading flow container. Nested containers need their own declaration.
It is also worth knowing that the spec has an open issue about whether the property should apply to tables, and the design notes record the principle the whole feature is built on: "Linear navigation, focus sequencing order, and screen-reader order should always match, because there are users who use them together."
Where this bites in real layouts
Auto-placed layouts create this mismatch without anyone writing order at all. In CSS grid lanes — the masonry layout mode — a spanning item that does not fit can land lower than items that come after it in the DOM, so the visual and DOM orders diverge purely from the algorithm. The Grid Level 3 spec points at reading-flow as the remedy, with the caveat that it is only appropriate "if the items do not have an inherent order".
That caveat is the decision rule. Search results, a photo wall, a tag cloud: no inherent order, so re-ordering for reading is honest. A numbered set of steps, a leaderboard, a chronological feed: the order carries meaning, so fix the source instead.
Shipping it today
Because it changes nothing visual, reading-flow degrades to exactly the situation you already had: engines that do not support it keep the DOM order. That makes it safe to add now and get the benefit for Chromium users, as long as you have not used it to justify markup that reads badly elsewhere. Wrap it in @supports (reading-flow: grid-rows) only if you want to change other properties alongside it.
Support here is time-sensitive. Check it again before treating it as the accessibility story for a layout.
FAQ
Does reading-flow fix the order for screen readers as well as the Tab key?
That is its stated purpose: it "controls the order in which elements are rendered to speech or are navigated to when using (linear) sequential navigation methods". Both should move together.
Can I use it instead of tabindex?
They solve different problems. A positive tabindex moves an element within the focus order of the whole page, which is hard to keep correct as a page grows; reading-flow reorders within one container and leaves everything outside it alone.
Does reading-flow: grid-rows work on a flex container?
No. Each keyword names the layout mode it takes effect on. A grid-* value on a flex container does nothing.
Is it Baseline?
No. At the time of writing it is Chromium-only, with no support in Firefox or Safari.
What happens to items I have not given a reading-order?
They stay in ordinal group 0, the initial value, and are ordered among themselves by whatever reading-flow value the container has.
Sources
- CSS Display Module Level 4 — CSS Working Group, Editor's Draft; the
reading-flowandreading-orderdefinitions, theorderadvisement and the design notes - CSS Grid Layout Module Level 3 — CSS Working Group; the reordering-and-accessibility guidance for grid lanes
- MDN browser compatibility data —
css/properties/reading-flow.json,css/properties/reading-order.json
More to read
- AI
AI · · 8 min read
Translating text in the browser, with no API key and no server
Chrome ships a Translator and a LanguageDetector on the web platform. What the spec guarantees, the user gesture it demands, and why on-device is not promised.
- Tech
Tech · · 7 min read
Animating between two separate pages, with two lines of CSS
A multi-page site can cross-fade between navigations with no JavaScript and no router. The five conditions, the descriptor both pages need, and what still needs script.