Flexbox & the keyboard navigation disconnect

CSS Flexbox can create a disconnect between the DOM order and visual presentation of content, causing keyboard navigation to break. For this reason, the CSS Flexible Box Layout module warns against resequencing content logic, but asking authors not to use flexbox in this way seems illogical in itself.

TLDR: The only viable way (in my opinion) for the flexbox disconnect to be resolved, is in the browser (as with the Firefox “bug”) and the accessibility tree.

The problem

When content is presented in sequence, we expect to read it and navigate through it in a logical order. In the West this is typically top to bottom/left to right. In the following example, the expected order is “1”, “2”, “3”.

<div>
    <a href="/">One</a>
    <br>
    <a href="/">Two</a>
    <br>
    <a href="/">Three</a>
</div>

With flexbox it is possible to display content in a different visual order, without changing the DOM order. Items inside a flex container can be visually sequenced using the CSS order property. In the following example, the visual order is “3, “2”, “1”, but the DOM order is still “1”, “2”, “3”.

<div style="display: flex;">
    <a href="/" style="order: 3;">One</a>
    <br>
    <a href="/" style="order: 2;">Two</a>
    <br>
    <a href="/" style="order: 1;">Three</a>
</div>

When you use the tab key to move through the content, there is a disconnect between the visual order and the keyboard navigation (DOM) order. In this simple example it’s mildly awkward, but in a situation where flexbox is used to layout a complex interface it could make things horribly unusable.

The options

There are two things that can be done to bypass the DOM order at the code level, but neither of them solve the flexbox disconnect problem.

The tabindex attribute

The HTML tabindex attribute can be used to impose a specific DOM order on content. If the tabindex values match the corresponding order property values, the visual and DOM orders can be brought into alignment. In the following example the visual and keyboard order are both “3”, “2”, “1”.

<div style="display: flex;">
    <a href="/" style="order: 3;" tabindex="3">One</a>
    <br>
    <a href="/" style="order: 2;" tabindex="2">Two</a>
    <br>
    <a href="/" style="order: 1;" tabindex="1">Three</a>
</div>

The problem is that tabindex is scoped to the document. If the above code were included in a page, it would completely hijack the tab order. The three items with tabindex would be the first three things on the page to receive keyboard focus, irrespective of their overall location in the DOM structure. In other words, you can use tabindex to solve the flexbox disconnect, but only by pushing the problem up to the document level.

The aria-flowto attribute

The aria-flowto attribute can be used to provide an alternative keyboard navigation for screen reader users. In the following example the visual and aria-flowto orders are both “3”, “2”, “1”, and the DOM order is “1”, “2”, “3”.

<div style="display: flex;">
    <a href="/" style="order: 3;" id="i1">One</a>
    <br>
    <a href="/" style="order: 2;" id="i2" aria-flowto="i1">Two</a>
    <br>
    <a href="/" style="order: 1;" id="i3" aria-flowto="i2">Three</a>
</div>

The first reason aria-flowto does not solve the flexbox disconnect, is because it complicates rather than simplifies the problem. It introduces a third mode of navigation, and one that is only applicable to screen reader users (who must use specific keyboard commands to benefit from it).

The second problem is that aria-flowto has extremely poor accessibility support. Of the various popular browser and screen reader combinations, only Jaws with Firefox, or Narrator with Edge and IE, has support for aria-flowto.

The browser

A more promising option is for the browser to handle the flexbox disconnect.

Firefox realigns the tab order to match the visual order (based on the order property). When you use the tab key to move through the above example, focus moves to each link in the order in which it appears on screen.

Interestingly this behaviour is considered to be an implementation bug because it’s contrary to the FlexBox specification. This may explain why neither the DOM order or the accessibility tree seem to be altered (the realignment appears to happen elsewhere), but the upshot is that it solves the disconnect problem for people using the tab key to navigate content.

It doesn’t entirely solve the flexbox disconnect problem for screen reader users though. If a screen reader user tabs through the content the realignment happens as described, but screen readers that use a virtual buffer will also present the content in DOM order when the virtual mode is used. It’s worth mentioning that this is the case with all the options mentioned here, and it’s likely to be the case with any future option that doesn’t rearrange the DOM and/or accessibility tree.

So the current situation is unsatisfactory for developers and users alike. As Rich Schwerdtfeger explains, the CSS “don’t use it” recommendation is unacceptable, and hacking things at the code level isn’t desirable or worthwhile in this instance.

The Firefox implementation/bug seems to have merit though. It isn’t perfect, but it comes close to solving the problem with a minimum of fuss and bother for developers and a reasonable outcome for keyboard users.

Related reading

7 comments on “Flexbox & the keyboard navigation disconnect”

Skip to Post a comment
  1. Comment by AlastairC

    I agree that Firefox’s “bug” was actually a good start, but this will be even more important for CSS grids.

    The visual order (that you’d expect as a visual keyboard user) is even less likely to match the DOM order, otherwise you wouldn’t be using grids. Also, the grids are 2 dimensional, so it is more complex than the flexbox 1 dimensional approach.

    Any solution relying on authors to define a particular order for accessibility will fail because for most devs, it isn’t on the radar. We see that even without new layout methods, where the visual order can be all-over the place.

    I think what is needed is what Opera used to do for keyboard navigation, where it had an algorithm for following a visual order around the page, top left to bottom right (for LTR languages at least).

  2. Comment by Jonathan Fielding

    I have had this problem before and the only solution I had was to include my content in the page twice and show and hide as appropriate. An alternative might be to do your order desktop first and then reorder on mobile where tab order isn’t used for keyboard tabbing but still a rubbish solution.

    I think the spec needs updating so that visual order is the order in that we tab through the page which is exactly how you described Firefoxs behaviour. So many layouts I have built would be easier if this were the case.

  3. Comment by AlastairC

    I’d be careful assuming that tabbing order doesn’t affect mobile. Some smaller windows devices and iOS devices with a keyboard might use tabbing.

  4. Comment by ChrisB

    Neither tabindex nor aria-flowto can solve the problem of different ordering for different viewport sizes, if you apply order within specific media queries only.

  5. Comment by Curtis Wilcox

    Jonathan, isn’t iOS VoiceOver’s swipe-right gesture equivalent to a keyboard Tab press?

    I assume iOS’s “next” and “previous” arrows at the top of the on-screen keyboard also follow the DOM order to move focus between form fields.

  6. Comment by Kevin Mack

    With the growth and support of flexbox, it only makes sense that this becomes a solution for browsers to help support. Ideally our DOM *is* constructed in the way that it should be read from top-tp-bottom, but due to various contexts (e.g. screen size) we may have to reposition certain containers to a lower priority when these contextual factors come into play and the browsers should help us fulfill needs for all users across all devices and limitations.

  7. Comment by sue

    Could you please explain more example with “aria-flowto” attribute? I would be grateful 🙂 I dont understand how aria order is 3->2->1, because the first step in code, which aria sees, is 1…?

Comment on this post

Will not be published
Optional