ZYVOPMulti-Platform Sync
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZYVOP
The Developer Publishing Hub
PrivacyTermsGuidelinesDMCACommunity
© 2026 ZyVOP
HomeCSS reading-flow: Fix the Tab Order Your Layout Broke

CSS reading-flow: Fix the Tab Order Your Layout Broke

Danny Holloran
Danny Holloran
Senior Developer
September 19, 2026
3 min read
CSS reading-flow: Fix the Tab Order Your Layout Broke
#Accessibility#CSS

Put a card grid on a page, give one card grid-row: 1 / 3 so it sits up top as the hero, and the layout looks exactly right. Then press Tab. Focus lands somewhere in the middle of the page, jumps back up to the hero, skips sideways, and generally behaves like it is reading a different document than the one on screen. It is, in a way: the browser follows DOM order, and you just spent an afternoon making the visual order disagree with it.

This has been the standing tax on grid and flexbox since they shipped. order, row-reverse, grid-area, and auto-placement all move boxes around without moving a single node, and sequential focus navigation never got the memo. The usual workaround was positive tabindex, which trades one bug for a worse one. reading-flow and reading-order are the CSS Display Level 4 answer, and they are in browsers now.

The disconnect, in eight lines

Here is the smallest version of the problem. Three links in a flex container, reversed, with one of them reordered on top of that:

.box {
  display: flex;
  flex-direction: row-reverse;
}

.box :nth-child(1) {
  order: 2;
}

Visually you read One, Three, Two. Tab through it and you get One, Two, Three, because that is the source order. Nothing is broken according to the spec, and everything is broken according to the person using a keyboard.

One property fixes it:

.box {
  reading-flow: flex-visual;
}

Focus order becomes One, Three, Two, matching what is on screen in a left-to-right writing mode. If you would rather keep the reversed intent instead of the visual left-to-right sweep, reading-flow: flex-flow gives you Two, Three, One. Both respect order.

Picking the right keyword

reading-flow is set on the container, and the value you want depends on the layout it creates:

  • normal (the default) keeps DOM order, exactly as before.
  • flex-visual follows the visual order of flex items, accounting for writing mode.
  • flex-flow follows the flex-flow direction.
  • grid-rows walks the grid visually, row by row.
  • grid-columns walks it column by column.
  • grid-order follows the modified order when order is applied to grid items, and behaves like normal when it is not.
  • source-order works in grid, flex, and block containers, and is the one that enables manual overrides.

That last one pairs with reading-order, which is set on the item and takes an integer. It is the escape hatch for the case no keyword describes, such as an absolutely positioned item in a block container that visually sits above everything else:

.wrapper {
  display: block;
  reading-flow: source-order;
}

.wrapper .top {
  reading-order: -1;
}

Focus visits .top first, then falls back to source order for the rest. reading-order only does anything inside a container whose reading-flow is not normal, so the two are always used together.

Why this is not just tabindex with extra steps

You could approximate the flex example with tabindex="1", tabindex="2", tabindex="3" plus an aria-owns attribute to keep the accessibility tree in sync. People have been doing exactly that for years, and it is fragile for a specific reason: positive tabindex values are document-global. Another component elsewhere on the page with tabindex="1" joins the same bucket, and focus starts teleporting between unrelated regions of the page.

reading-flow avoids that by making the container a focus scope owner. Sequential navigation visits every focusable element inside the container before moving on, and the direct children are ordered by the property rather than by any positive tabindex on them, which is ignored for ordering purposes. The reordering also applies to how the container's children are exposed to assistive tech, not just to the Tab key, so the screen reader and the keyboard agree.

One sharp edge worth knowing: an element with display: contents inherits reading-flow from its layout parent and becomes a valid reading flow container itself. If you use display: contents as a wrapper, check your focus order after adding this.

Where support stands

Chrome has shipped both properties since version 137, Safari added support in 26.4, and Firefox still has it behind a flag as of September 2026 — so this is not Baseline yet. It degrades cleanly, though: browsers that do not understand reading-flow simply keep using DOM order, which is what they do today.

That makes it a real progressive enhancement, with one caveat. reading-flow is a fix for the cases where source order genuinely cannot match the visual layout, not a license to stop caring about markup order. Get the DOM close to right first, then reach for this to close the gap that grid and flexbox opened. The MDN pages for reading-flow and reading-order have the full value tables, and Chrome's reading-flow examples are worth tabbing through with your eyes closed.

Comments (0)

Join the discussion by logging into your account.

Danny Holloran
Danny Holloran

Senior Developer

Senior Frontend & Fullstack Developer with 14+ years building performant, scalable web applications. Passionate about architecture, mentorship, and finding the right tool for the job.

Subscribe to Danny Holloran's Newsletter

Direct email dispatches when new stories are published. Zero algorithms.

Danny Holloran
Like
Love
Clap
Fire
Party
Wow

More from Danny Holloran

View profile

WebNN: The Only Web API That Can Reach Your NPU

Almost every laptop shipped in the last two years has a neural processing unit sitting idle. WebNN is the only web standard that can actually talk to it, and it just hit an updated Candidate Recommendation.

4 minSep 15

GraphQL @oneOf: Exactly One Input, Enforced by the Schema

OneOf Input Objects landed in the September 2025 GraphQL spec, which means the exactly-one-of-these-arguments rule you've been enforcing in resolver code is now something the type system can do for you.

3 minSep 14

node --test: The Test Runner You Already Have Installed

Node's built-in test runner has been stable since v20 and now handles mocking, coverage, watch mode, and TypeScript files. Here's what it does well and where it still falls short.

3 minSep 12

Svelte Snippets: Reuse Markup Without a New Component

Snippets let you define reusable chunks of markup inline and render them like functions. They replace slots, kill the let: directive, and mean you stop extracting a component every time you repeat six lines.

3 minSep 8

CSS scroll-state() Queries: Styling Stuck, Snapped, and Scrollable

Sticky headers, snapped carousel slides, and scroll shadows have all been JavaScript jobs for a decade. Scroll-state container queries hand that work back to CSS.

3 minSep 7