The reading-flow
and reading-order
proposed CSS properties are designed to specify the source order of HTML elements in the DOM tree, or in simpler terms, how accessibility tools deduce the order of elements. You’d use them to make the focus order of focusable elements match the visual order, as outlined in the Web Content Accessibility Guidelines (WCAG 2.2).
To get a better idea, let’s just dive in!
(Oh, and make sure that you’re using Chrome 137 or higher.)
reading-flow
reading-flow
determines the source order of HTML elements in a flex, grid, or block layout. Again, this is basically to help accessibility tools provide the correct focus order to users.
The default value is normal
(so, reading-flow: normal
). Other valid values include:
flex-visual
flex-flow
grid-rows
grid-columns
grid-order
source-order
Let’s start with the flex-visual
value. Imagine a flex row with five links. Assuming that the reading direction is left-to-right (by the way, you can change the reading direction with the direction
CSS property), that’d look something like this:
Now, if we apply flex-direction: row-reverse
, the links are displayed 5-4-3-2-1. The problem though is that the focus order still starts from 1 (tab through them!), which is visually wrong for somebody that reads left-to-right.
But if we also apply reading-flow: flex-visual
, the focus order also becomes 5-4-3-2-1, matching the visual order (which is an accessibility requirement!):
<div>
<a>1</a>
<a>2</a>
<a>3</a>
<a>4</a>
<a>5</a>
</div>
div {
display: flex;
flex-direction: row-reverse;
reading-flow: flex-visual;
}
To apply the default flex behavior, reading-flow: flex-flow
is what you’re looking for. This is very akin to reading-flow: normal
, except that the container remains a reading flow container, which is needed for reading-order
(we’ll dive into this in a bit).
For now, let’s take a look at the grid-y values. In the grid below, the grid items are all jumbled up, and so the focus order is all over the place.
We can fix this in two ways. One way is that reading-flow: grid-rows
will, as you’d expect, establish a row-by-row focus order:
<div>
<a>1</a>
<a>2</a>
<a>3</a>
<a>4</a>
<a>5</a>
<a>6</a>
<a>7</a>
<a>8</a>
<a>9</a>
<a>10</a>
<a>11</a>
<a>12</a>
</div>
div {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 100px;
reading-flow: grid-rows;
a:nth-child(2) {
grid-row: 2 / 4;
grid-column: 3;
}
a:nth-child(5) {
grid-row: 1 / 3;
grid-column: 1 / 3;
}
}
Or, reading-flow: grid-columns
will establish a column-by-column focus order:
reading-flow: grid-order
will give us the default grid behavior (i.e., the jumbled up version). This is also very akin to reading-flow: normal
(except that, again, the container remains a reading flow container, which is needed for reading-order
).
There’s also reading-flow: source-order
, which is for flex, grid, and block containers. It basically turns containers into reading flow containers, enabling us to use reading-order
. To be frank, unless I’m missing something, this appears to make the flex-flow
and grid-order
values redundant?
reading-order
reading-order
sort of does the same thing as reading-flow
. The difference is that reading-order
is for specific flex or grid items, or even elements in a simple block container. It works the same way as the order
property, although I suppose we could also compare it to tabindex
.
Note: To use reading-order
, the container must have the reading-flow
property set to anything other than normal
.
I’ll demonstrate both reading-order
and order
at the same time. In the example below, we have another flex container where each flex item has the order
property set to a different random number, making the order of the flex items random. Now, we’ve already established that we can use reading-flow
to determine focus order regardless of visual order, but in the example below we’re using reading-order
instead (in the exact same way as order
):
div {
display: flex;
reading-flow: source-order; /* Anything but normal */
/* Features at the end because of the higher values */
a:nth-child(1) {
/* Visual order */
order: 567;
/* Focus order */
reading-order: 567;
}
a:nth-child(2) {
order: 456;
reading-order: 456;
}
a:nth-child(3) {
order: 345;
reading-order: 345;
}
a:nth-child(4) {
order: 234;
reading-order: 234;
}
/* Features at the beginning because of the lower values */
a:nth-child(5) {
order: -123;
reading-order: -123;
}
}
Yes, those are some rather odd numbers. I’ve done this to illustrate how the numbers don’t represent the position (e.g., order: 3
or reading-order: 3
doesn’t make it third in the order). Instead, elements with lower numbers are more towards the beginning of the order and elements with higher numbers are more towards the end. The default value is 0
. Elements with the same value will be ordered by source order.
In practical terms? Consider the following example:
div {
display: flex;
reading-flow: source-order;
a:nth-child(1) {
order: 1;
reading-order: 1;
}
a:nth-child(5) {
order: -1;
reading-order: -1;
}
}
Of the five flex items, the first one is the one with order: -1
because it has the lowest order
value. The last one is the one with order: 1
because it has the highest order
value. The ones with no declaration default to having order: 0
and are thus ordered in source order, but otherwise fit in-between the order: -1
and order: 1
flex items. And it’s the same concept for reading-order
, which in the example above mirrors order
.
However, when reversing the direction of flex items, keep in mind that order
and reading-order
work a little differently. For example, reading-order: -1
would, as expected, and pull a flex item to the beginning of the focus order. Meanwhile, order: -1
would pull it to the end of the visual order because the visual order is reversed (so we’d need to use order: 1
instead, even if that doesn’t seem right!):
div {
display: flex;
flex-direction: row-reverse;
reading-flow: source-order;
a:nth-child(5) {
/* Because of row-reverse, this actually makes it first */
order: 1;
/* However, this behavior doesn’t apply to reading-order */
reading-order: -1;
}
}
reading-order
overrides reading-flow
. If we, for example, apply reading-flow: flex-visual
, reading-flow: grid-rows
, or reading-flow: grid-columns
(basically, any declaration that does in fact change the reading flow), reading-order
overrides it. We could say that reading-order
is applied after reading-flow
.
What if I don’t want to use flexbox or grid layout?
Well, that obviously rules out all of the flex-y and grid-y reading-flow
values; however, you can still set reading-flow: source-order
on a block element and then manipulate the focus order with reading-order
(as we did above).
How does this relate to the tabindex
HTML attribute?
They’re not equivalent. Negative tabindex
values make targets unfocusable and values other than 0
and -1
aren’t recommended, whereas a reading-order
declaration can use any number as it’s only contextual to the reading flow container that contains it.
For the sake of being complete though, I did test reading-order
and tabindex
together and reading-order
appeared to override tabindex
.
Going forward, I’d only use tabindex
(specifically, tabindex="-1"
) to prevent certain targets from being focusable (the disabled
attribute will be more appropriate for some targets though), and then reading-order
for everything else.
Closing thoughts
Being able to define reading order is useful, or at least it means that the order
property can finally be used as intended. Up until now (or rather when all web browsers support reading-flow
and reading-order
, because they only work in Chrome 137+ at the moment), order
hasn’t been useful because we haven’t been able to make the focus order match the visual order.
What We Know (So Far) About CSS Reading Order originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.