Published: May 1, 2025
The CSS reading-flow
and reading-order
properties are available from Chrome 137.
This post explains the reasons behind the design of these properties and some
brief details to get you started with them.
Layout methods such as grid and flex have transformed frontend development, however their flexibility can cause a problem for some users. It's very easy to create a situation where the visual order mismatches the source order in the DOM tree. As this source order is what the browser follows if you navigate the site using a keyboard, some users can encounter unexpected jumping around as they navigate around a page.
The reading-flow
and reading-order
properties have been designed and added to
the CSS Display
specification, to try and
solve this long standing problem.
reading-flow
The reading-flow
CSS property controls the order that elements in a flex, grid,
or block layout are exposed to accessibility tools and how they get focused
using linear sequential navigation methods.
It takes one keyword value, with a
default of normal
, which keeps the behavior of ordering elements in DOM order.
To use it inside a flex container, set its value to either flex-visual
or
flex-flow
. To use it inside a grid container, set its value to either
grid-rows
, grid-columns
, or grid-order
.
reading-order
The reading-order
CSS property lets you manually override the order of
items within a reading flow container. To use this property inside a grid, flex,
or block container, set the reading-flow
value on the container to
source-order
and set the individual item's reading-order
to an integer value.
Example in flexbox
For example, you might have a flex layout container with three elements in reverse row order, and also want to use the order property to re-shuffle that ordering.
<div class="box">
<a href="#">One</a>
<a href="#">Two</a>
<a href="#">Three</a>
</div>
.box {
display: flex;
flex-direction: row-reverse;
}
.box :nth-child(1) {
order: 2;
}
You can try navigating these elements using the TAB key to find the next focusable element and TAB+SHIFT keys to find the previous focusable element. This follows the items in source order: One, Two, Three.
From the end user's perspective, this doesn't make sense and can be very confusing. The same thing happens if we use an accessibility spatial navigation tool to navigate across the page.
To fix this, set the reading-flow
property:
.box {
reading-flow: flex-visual;
}
The focus order is now: One, Three, Two. This is the same as the visual order you would get if reading in English from left to right.
If instead, you prefer keeping the focus order as it was originally meant, in reverse order, you can set:
.box {
reading-flow: flex-flow;
}
The focus order is now the reverse flex order: Two, Three, One. In
both cases, the CSS order
property is accounted for.
Example with grid layout
To see how this works in a grid, imagine that you are creating a layout with CSS grid auto-placed items with twelve focusable areas.
<div class="wrapper">
<a href="#">One</a>
<a href="#">Two</a>
<a href="#">Three</a>
<a href="#">Four</a>
<a href="#">Five</a>
<a href="#">Six</a>
<a href="#">Seven</a>
<a href="#">Eight</a>
<a href="#">Nine</a>
<a href="#">Ten</a>
<a href="#">Eleven</a>
<a href="#">Twelve</a>
</div>
You want the fifth child to take the largest space at the very top, followed by the second child toward the middle of the grid. All other children can be auto placed within the grid following a column template.
.wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 100px;
}
.wrapper a:nth-child(2) {
grid-column: 3;
grid-row: 2 / 4;
}
.wrapper a:nth-child(5) {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
Try navigating these elements using the TAB key to find the next focusable element and TAB+SHIFT keys to find the previous focusable element. This follows the items in source order: One to Twelve.
To fix this, set the reading-flow
property:
.wrapper {
reading-flow: grid-rows;
}
The focus order is now: Five, One, Three, Two, Four, Six, Seven, Eight, Nine, Ten, Eleven, Twelve. It follows the visual order, row by row.
If you want to have the reading flow follow the column order instead, you can
use the grid-columns
keyword value instead. The focus order then becomes Five,
Six, Nine, Seven, Ten, One, Two, Eleven, Three, Four, Eight, Twelve.
.wrapper {
reading-flow: grid-columns;
}
You can also try to use grid-order
. The focus order stays as One to Twelve.
This is because there was no CSS order set on any item.
A block container using reading-order
The reading-order
property lets you specify when in the reading flow an item
should be visited, overriding the order set by the reading-flow
property. It
only takes effect on a valid reading flow container, when the reading-flow
property is not normal
.
.wrapper {
display: block;
reading-flow: source-order;
}
.top {
reading-order: -1;
inset-inline-start: 50px;
inset-block-start: 50px;
}
The following block container contains five items. There are no layout rules that reorder the elements from its source order, but there is one out of flow item that should be visited first.
<div class="wrapper">
<a href="#">Item 1</a>
<a href="#">Item 2</a>
<a href="#">Item 3</a>
<a href="#">Item 4</a>
<a class="top" href="#">Item 5</a>
</div>
By setting this item's reading-order
to -1
, the focus order visits it first
before falling back to source order for the rest of the reading flow items.
You can find more examples on the chrome.dev site.
Interaction with tabindex
Historically, developers have used the HTML tabindex
global attribute to make
HTML elements focusable and determine the relative ordering for sequential focus
navigation. However, this attribute has many drawbacks and accessibility
concerns. The main concern is that the tabindex-ordered focus navigation created
by using positive tabindex is not recognized by the accessibility tree. When
used incorrectly, you might end up with jumpy focus order that mismatches the
experience on a screen reader. To fix that, track the ordering using the
aria-owns HTML attribute.
In the previous flex example, to get the same result as using
reading-flow: flex-visual
, you might do the following.
<div class="box" aria-owns="one three two">
<a href="#" tabindex="1" id="one">One</a>
<a href="#" tabindex="3" id="two">Two</a>
<a href="#" tabindex="2" id="three">Three</a>
</div>
But, what happens if another element outside the container also has tabindex=1
?
Then, all elements with tabindex=1
will be visited together, before we move to
the next incremental tabindex value. This jumpy sequential navigation will
result in bad user experience. Hence, accessibility experts recommend avoiding
positive tabindex. We
tried to fix this when designing reading-flow
.
A container that has the reading-flow
property set becomes a focus scope owner.
This means it scopes the sequential focus navigation to visit every element
inside the container before moving to the next focusable element in a web
document. Further, its direct children are ordered using the reading-flow
property and positive tabindex are ignored for ordering purposes. It is still
possible to set a positive tabindex on the descendants of a reading-flow item.
Note that an element with display: contents
that inherits the reading-flow
property
from its layout parent will also be a valid reading flow container. Be
mindful of this when designing your site. Read more about this in our
request for feedback on reading-flow
and display: contents
.
Let us know
Try out the examples in this post and in the
reading-flow
examples on chrome.dev, and use these CSS
properties on your sites. If you have any feedback, raise it as an issue on the
CSS Working Group GitHub repo. If
you have feedback specifically on the tabindex and focus scoping behavior, raise
it as an issue with the HTML WHATNOT GitHub
repo. We would love your feedback on
this feature.