Growing
Last updated:

Reclaiming the Flow: Mastering Visual vs. DOM Order with @reading-order

Your CSS is lying to some of your users. Here's the property that fixes it.

The Accessibility Gap: When Visual Order Betrays Logical Order

For a long time, developers have been able to put together the strangest of layouts that have come from the dark corners of the design team with very little pushback. Properties like flex-direction, grid-template-areas and the order property have given us a monumental amount of control when dealing with the visual presentation of the elements on the screen, and we can rearrange content to adapt to different screen sizes and user preference with little effort.

Sometimes however, that ability comes with a sidestep of accessibility and performance concerns, and that really isn’t acceptable at all. We would warn that a layout might affect accessibility, but in the worst cases it can met with a shrug, depending on how much the team cares about providing accessible solutions in the first place. In my current position, accessibility concerns are met with a lot of discussion and push back to find a better path - so the prior ‘shrug’ example is not a reflection of how our projects are dealt with. The main challenge has been that while we can change how elements look on the screen, their true order in the DOM is the ‘golden truth’. We’ll go through a couple of examples to help illustrate this.

Example 1: Our card layout

If we have for example, a traditional card layout that has 3 elements; a date (the highest semantic priority), a title, and an image (the lowest semantic priority), and we’ve used CSS to reorder them differently (let’s say; the image, then the date, then the title). We would have this;

.reading-order-card date {
    order: 2;
}

.reading-order-card span,
.reading-order-card > a {
    order: 3;
}

.reading-order-card img {
    order: 1;
}

Card heading

Laborum sint laborum nostrud est. Est exercitation et occaecat ut proident non voluptate et. Laborum non id voluptate tempor ea anim anim eu irure laborum velit labore ullamco. Non ipsum labore consequat adipisicing amet ut reprehenderit.

Visually, we’ve got our card layout looking like this; an image, then a date, then a title. Pretty generic stuff, but when a screen reader user traverses this layout, the order of the content is not what we would expect. The image is announced first, then the date, then the title. This is because the screen reader is reading the content in the order of the DOM, not the visual order.

Visual order vs DOM order: the gap your users fall into

When CSS reorders content visually, keyboard and screen reader users still follow the DOM. Use Tab to navigate the cards below and feel the difference.

The cards below are visually ordered CTA → Article A → Article B using CSS order. But the DOM hasn't changed - assistive tech and keyboard users still encounter them as Article A → Article B → CTA. Tab through to see the disconnect.
What sighted users see: CTA → Article A → Article B
What keyboard/screen reader users get: Article A → Article B → CTA

The CTA appears first visually via order: 1 - but it's third in the DOM. Tab key follows the DOM.
/* Visual order achieved with CSS order - */
/* but DOM (and focus) order is unchanged */

.card-a { order: 2; } /* DOM: 1st, visual: 2nd */
.card-b { order: 3; } /* DOM: 2nd, visual: 3rd */
.card-cta { order: 1; } /* DOM: 3rd, visual: 1st ⚠️ */
Article A (DOM 1st) Article B (DOM 2nd) CTA (DOM 3rd)
Focus follows DOM order. The CTA appears visually first but receives focus last - a WCAG 2.4.3 failure.
Adding reading-order: auto to the flex container tells the browser to follow visual order for focus and reading too. Now DOM order, focus order, and visual order all match. Tab through - the sequence is consistent.
Visual order: CTA → Article A → Article B (unchanged)
Focus + reading order: CTA → Article A → Article B (now matches )

reading-flow: flex-visual on the container + reading-order integers on each child tell the browser's Accessibility Tree to follow visual order instead of DOM order.
/* Same visual order - but now focus matches */
.cards {
  display: flex;
  reading-flow: flex-visual; /* ← on the container */
}

.card-a { order: 2; reading-order: 2; }
.card-b { order: 3; reading-order: 3; }
.card-cta { order: 1; reading-order: 1; }
CTA (visual 1st) Article A (visual 2nd) Article B (visual 3rd)
Focus follows visual order. Keyboard users and screen reader users now experience the same sequence as sighted users.
Card DOM position Visual position (order) Focus position (reading-order)
CTA 3rd 1st 1st
Article A 1st 2nd 2nd
Article B 2nd 3rd 3rd

Example 2: Our grid layout

If we have for example, a layout that is 3 elements (Article A, Article B and a CTA) in that order in the DOM, and we’ve used CSS to reorder them differently (let’s say; the CTA, Article A and Article B). We would have this;

.reading-order-example-2 {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-areas:
        "cta article-a article-b"
        "article-c cta-2 article-d"
}

.reading-order-example-2 .reading-order-card--1 {
    grid-area: article-a;
}

.reading-order-example-2 .reading-order-card--2 {
    grid-area: article-b;
}

.reading-order-example-2 .reading-order-card--3 {
    grid-area: cta;
}

.reading-order-example-2 .reading-order-card--4 {
    grid-area: article-c;
}

.reading-order-example-2 .reading-order-card--5 {
    grid-area: article-d;
}

.reading-order-example-2 .reading-order-card--6 {
    grid-area: cta-2;
}
20th October 2025This is the card that is the first item in the DOM
20th October 2025This is the card that is the second item in the DOM
20th October 2025This is the card that is the first item in the DOM
20th October 2025This is the card that is the second item in the DOM

What’s the correct order for these examples? Visually they are correct - they’ve done what we want it to do in that specific order to appease our designer overlords. However, for a user navigating the site through the use of a screen reader or a keyboard, this might be a bit of a contradiction. Typically screen readers such as NVDA, JAWS or Voiceover traverse the DOM and read out the content as it is set there, disregarding any extra information that might be in place to reorder it, announcing the CTA, then the Article A, then the Article B in that order. If we use the tab key to traverse the DOM, it will once again follow the DOM order, rather than our re-ordered items. Try using the tab key on this page to navigate through the section above, and notice that the focus outline is applied to Article A before the CTA. So we can see by attempting to replicate our design and using order or grid-template-areas, that we’ve actually broken the accessibility of the page and left behind come accessibility debt to fix later on, and that debt increases with every time we use order or grid-template-areas and don’t account for the accessibility implications. That’s also just from a developer’s perspective, considering the user experience is even worse - users who rely on keyboard navigation or screen readers only have the DOM as their map and our CSS decisions often discard that map entirely.

Visual order vs DOM order: the gap your users fall into

When CSS reorders content visually, keyboard and screen reader users still follow the DOM. Use Tab to navigate the cards below and feel the difference.

Visual layout

DOM order: Feature(1) · Article A(2) · Article B(3) · CTA(4) - CTA is last in the DOM but placed top-left visually.

The problem: CTA is placed top-left via grid-template-areas, but it's the 4th item in the DOM. Tab key ignores grid placement entirely - it follows DOM order. A keyboard user hits Feature → Article A → Article B → CTA: the opposite of what the layout implies.

Live layout - try tabbing through

DOM: 1st Feature

The hidden cost of visual reordering in modern CSS layouts

Every time we reach for order or grid-template-areas to move content around the screen, we're making a silent promise to sighted users that we may be breaking for everyone else.

Read feature →
DOM: 2nd Article A

WCAG 2.4.3 and the focus order criterion

Focus order is a Level A requirement, meaning it applies to the broadest range of users and content types.

Read article →
DOM: 3rd Article B

Testing keyboard navigation: a practical checklist

Tab through your layout. Does focus move in a logical sequence? Does it match what a sighted user would expect?

Read article →

Tab focus sequence

Feature (DOM 1) Article A (DOM 2) Article B (DOM 3) CTA (DOM 4)
Focus reaches the CTA last - but it's visually first. A keyboard user navigates the grid in a sequence that contradicts what they see. WCAG 2.4.3 failure.
The fix: Add reading-flow: grid-rows to the container. The browser now reads focus order row by row, matching the visual grid - CTA → Feature → Article A → Article B. DOM order is unchanged; only focus order is corrected.

Live layout - try tabbing through

DOM: 1st Feature

The hidden cost of visual reordering in modern CSS layouts

Every time we reach for order or grid-template-areas to move content around the screen, we're making a silent promise to sighted users that we may be breaking for everyone else.

Read feature →
DOM: 2nd Article A

WCAG 2.4.3 and the focus order criterion

Focus order is a Level A requirement, meaning it applies to the broadest range of users and content types.

Read article →
DOM: 3rd Article B

Testing keyboard navigation: a practical checklist

Tab through your layout. Does focus move in a logical sequence? Does it match what a sighted user would expect?

Read article →

Tab focus sequence

CTA (visual top-left) Feature (visual top-right) Article A (visual btm-left) Article B (visual btm-right)
Focus now moves left-to-right, top-to-bottom - exactly how a sighted user reads the grid. DOM order is unchanged; reading-flow corrects the accessibility layer only.

The CSS

.editorial-grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	grid-template-areas:
	"cta feature" "art-a art-b";
	reading-flow: grid-rows; /* ← one line fixes it */
}

/* DOM order: feature · art-a · art-b · cta */ /* Focus order after fix: cta → feature → art-a → art-b */ .cell-feature { grid-area: feature; }
.cell-art-a { grid-area: art-a; }
.cell-art-b { grid-area: art-b; }
.cell-cta { grid-area: cta; } /* DOM: last, visual: top-left */ 

How the orders map

Card DOM position Visual position (order) Focus position (reading-order)
CTA 4th top-left 1st
Feature 1st top-right 2nd
Article A 2nd btm-left 3rd
Article B 3rd btm-right 4th

The Solution: reading-flow and reading-order

So how do we fix this? This design isn’t exactly a new trend, but being able to fix it whilst also being able to maintain the visual order of the elements is a bit of a challenge. There is either;

  • an outcome where visual design flexibility is put aside in favor of accessibility leading to a less than ideal user experience/ugly design decision, or
  • we over-engineer a solution to look to sync the DOM with the visual order to aid with our accessibility efforts, leading to some complex and fragile solutions.

Bit of a no-win situation really. Even worse, the difference between visual order and DOM order isn’t just an massive inconvenience, it’s also a W3C-recognized accessibility violation, specifically the WCAG 1.3.2: Meaningful Sequence and WCAG 2.4.3: Focus Order guidelines.

With these two principles in mind, it’s clear that our number one goal should be the consistency and predictability of the experience for all users. Breaking this, if it wasn’t clear enough by now which hopefully it is, can lead to confusion, frustration and ultimately exclusion. By using reading-flow and reading-order, we can allow for content that is presented the way our design is put together, and also meets the accessibility principles that we outlined above. All the while avoiding the pitfalls of using order or grid-template-areas, their accessibility implications and focus on only visually rearranging content.

It’s important to note at this stage that this isn’t to replace the use of order or grid-template-areas, if anything, it allows us to use them for purely visual purposes and still maintain the accessibility of the page knowing that they won’t fail WCAG guidelines.

reading-flow (on the container):

So, what is reading-flow? This property controls how the browser determines the default reading order of children that are with a flex, grid or block container. There are seperate applicable values that can be used on this property, depending on whether the container has one of those display properties set. These values are;

  • normal (default): Follows DOM order.
  • flex-visual: Prioritizes the visual order of flex items.
  • flex-flow: Follows the logical flow of flex items (e.g., flex-direction: row-reverse would reverse the reading order).
  • grid-rows: Reads grid items row by row.
  • grid-columns: Reads grid items column by column.
  • grid-order: Follows the explicit order values on grid items.
  • source-order: Explicitly forces DOM order, even in grid/flex.

reading-order (on the items):

So if we have reading-flow to do this, what does reading-order do differently? Well reading-order provides an integer value - similar to how order is used - that allows a manual override of the reading order of individual items that are inside a reading-flow container. The lower the value, the higher the priority of reading order. If reading-flow is not set on the parent then any reading-order values set are not taken into account, and is also disregarded if the reading-flow container is set to normal or source-order. This is where the power of reading-order comes in, as it directly instructs the browser’s Accessibility Tree to follow the visual order, rather than the static DOM order. This means that for interactive elements, the keyboard focus order is automatically fixed, eliminating the need for fragile JavaScript solutions or manually setting tabindex values.

Best Practices and Considerations

When using these properties, it’s best to not overuse it. For accessibility, the default DOM order is always the best - referring back to our ‘golden truth’ comment earlier. If there was to be a recommendation, it would be to only use this when there’s a justified reason for the reordering of elements and if the design absolutely couldn’t be achieved without that reordering occurring - after all, it’s an enhancement to solve specific layout challenges.

When we look at the support at the top of this page, support is still sparse, but it’s getting better. As of March 2026, Chrome and Edge have support for reading-flow and reading-order. The progressive enhancement story is actually straightforward: browsers without support simply fall back to DOM order, which is the baseline behaviour anyway. No special fallback code needed - just make sure your DOM order is as logical as possible, and reading-flow becomes an enhancement on top of that. It’s important to communicate this to the team and the stakeholders, and to have a plan in place to test the site thoroughly with assistive technologies to ensure that the site is still accessible.

Conclusion

Sometimes visual design and accessibility butt heads, and they have done for years. Now, with the introduction of reading-flow and reading-order, this conflict can be significantly reduced. As developers, we no longer need to choose between a beautiful design and an accessible one. We’re finally getting the right tools to do this without complex JavaScript solutions or manual tabindex management. This is a win for everyone.

That being said, this is still a work in progress, and support is more sparse than it should be given the importance of the feature. To help push it forward, developers need to advocate for it - test it, report issues, and make the case for prioritising it. The next generation of web layouts should be both beautiful and universally accessible. We have the tools now. Let’s use them.

Resources

The spec is still evolving - these are the working drafts worth bookmarking if you want to track changes as browser support improves.

Browser Support

Experimental

Chrome 137
Edge 137
Firefox Not supported
Safari Not supported