Use CSS reading-flow for logical sequential focus navigation

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.