![Di Zhang](https://web.dev/images/authors/dizhangg.jpg) Di Zhang ![Rachel Andrew](https://web.dev/images/authors/rachelandrew.jpg) Rachel Andrew Technical Writer [GitHub](https://github.com/rachelandrew) [LinkedIn](https://www.linkedin.com/in/rachelandrew) [Mastodon](https://front-end.social/@rachelandrew) [Bluesky](https://bsky.app/profile/rachelandrew.bsky.social) [Homepage](https://rachelandrew.co.uk)

<br />


Published: May 1, 2025

<br />

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](https://www.w3.org/TR/css-display-4/#reading-flow), 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 <kbd>TAB</kbd> key to find the next
focusable element and <kbd>TAB+SHIFT</kbd> keys to find the previous focusable element.
This follows the items in source order: One, Two, Three.
<iframe height="210" style="width: 100%;" scrolling="no" title="Flex reading-flow example (without reading-flow)" src="https://codepen.io/web-dot-dev/embed/LEEzoRq?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/LEEzoRq"&amp;amp;gt; Flex reading-flow example (without reading-flow)&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

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.
<iframe height="210" style="width: 100%;" scrolling="no" title="Flex reading-flow example (with reading-flow: flex-visual)" src="https://codepen.io/web-dot-dev/embed/gbbGJLp?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/gbbGJLp"&amp;amp;gt; Flex reading-flow example (with reading-flow: flex-visual)&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

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.
<iframe height="210" style="width: 100%;" scrolling="no" title="Flex reading-flow example (with reading-flow: flex-flow)" src="https://codepen.io/web-dot-dev/embed/dPPVEvE?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/dPPVEvE"&amp;amp;gt; Flex reading-flow example (with reading-flow: flex-flow)&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

## 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 <kbd>TAB</kbd> key to find the next focusable
element and <kbd>TAB+SHIFT</kbd> keys to find the previous focusable element. This follows
the items in source order: One to Twelve.
<iframe height="600" style="width: 100%;" scrolling="no" title="Grid reading-flow example (without reading-flow)" src="https://codepen.io/web-dot-dev/embed/gbbGJyg?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/gbbGJyg"&amp;amp;gt; Grid reading-flow example (without reading-flow)&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

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.
<iframe height="600" style="width: 100%;" scrolling="no" title="Grid reading-flow example (with reading-flow: grid-rows)" src="https://codepen.io/web-dot-dev/embed/QwwqRPQ?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/QwwqRPQ"&amp;amp;gt; Grid reading-flow example (with reading-flow: grid-rows)&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

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;
    }

<iframe height="600" style="width: 100%;" scrolling="no" title="Grid reading-flow example (with reading-flow: grid-columns)" src="https://codepen.io/web-dot-dev/embed/vEEewwG?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/vEEewwG"&amp;amp;gt; Grid reading-flow example (with reading-flow: grid-columns)&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

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>

<iframe height="210" style="width: 100%;" scrolling="no" title="reading-flow and reading-order in block layout" src="https://codepen.io/web-dot-dev/embed/raaGgXP?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/pen/raaGgXP"&amp;amp;gt; reading-flow and reading-order in block layout&amp;amp;lt;/a&amp;amp;gt; by web.dev (&amp;amp;lt;a href="https://codepen.io/web-dot-dev"&amp;amp;gt;@web-dot-dev&amp;amp;lt;/a&amp;amp;gt;) on &amp;amp;lt;a href="https://codepen.io"&amp;amp;gt;CodePen&amp;amp;lt;/a&amp;amp;gt;. </iframe>

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.

> [!NOTE]
> **Note:** If the reading order of two items is equivalent, the [`reading-flow`](https://drafts.csswg.org/css-display-4/#propdef-reading-flow) property breaks the tie.

You can find more examples on the [chrome.dev site](https://chrome.dev/reading-flow-examples/).

## 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](https://www.scottohara.me/blog/2019/05/25/tabindex.html). 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`](https://developer.chrome.com/blog/reading-flow-display-contents).

## Let us know

Try out the examples in this post and in the
[`reading-flow` examples on chrome.dev](https://chrome.dev/reading-flow-examples/), 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](https://github.com/w3c/csswg-drafts/issues). If
you have feedback specifically on the tabindex and focus scoping behavior, raise
it as an issue with the [HTML WHATNOT GitHub
repo](https://github.com/whatwg/html/issues). We would love your feedback on
this feature.