Skip to content

Focus order does not follow visual order in mobile layout of the package view #1069

@knowler

Description

@knowler

I’m noticing a failure of WCAG 2.2 SC 2.4.3 Focus Order (Level A) for the mobile layout of the package view:

If a web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.

I encountered this issue while testing with VoiceOver on iOS 26.2.1 (and iPadOS 26.2 in portrait mode).

The associated “Understanding Focus Order” document states:

For example, a screen reader user interacts with the programmatically determined reading order, while a sighted keyboard user interacts with the visual presentation of the web page. Care should be taken so that the focus order makes sense to both of these sets of users and does not appear to either of them to jump around randomly.

“[J]umping around randomly” is an apt description of what’s taking place. While it might be easy to assume that this would not affect a person using a screen reader, the truth is not everyone who uses a screen reader is totally blind.

The cause of this issue is how CSS grid is being used for both the mobile and tablet views:

/* Mobile: single column, sidebar above readme */
grid-template-columns: minmax(0, 1fr);
grid-template-areas:
'header'
'details'
'install'
'vulns'
'sidebar'
'readme';
}
/* Tablet/medium: header/install/vulns full width, readme+sidebar side by side */
@media (min-width: 1024px) {
.package-page {
grid-template-columns: 2fr 1fr;
grid-template-areas:
'header header'
'details details'
'install install'
'vulns vulns'
'readme sidebar';
grid-template-rows: auto auto auto auto 1fr;
}
}

This visual grid order does not match the DOM order. Unfortunately, there are very few cases where we can alter the visual order with grid or flexbox. With that said there are some solutions…

Possible solutions

First off, a non-solution to this problem is to use tabindex with positive values. This is not a good path to go down as it’s bound to break.

Let the visual grid order follow the DOM order

The easiest solution is to just let the visual order match the DOM order. I think this is worth considering given the fact that the DOM order is what’s presented to those using the desktop view. One improvement to the mobile experience to go along with this change could be to make the README section collapsible, so users could easily scroll past it.

Leverage shadow DOM composition

Shadow DOM composition using <slot> elements can alter the visual and focus order of the shadow host’s light DOM content (i.e. a shadow host composes two distinct trees—unlike Vue/React, the elements remain in place in the DOM). To do this we would need to watch the break point with JavaScript and then change which slots each part of the layout was assigned to either using declarative named slot assignment (i.e. the default) or imperative manual slot assignment. I imagine this would be better for rendering performance than moving around the light DOM with Vue as the light DOM would remain in place (minimizing DOM mutations).

I will admit that this solution is more complex and I’d argue for the first solution before we consider it.

Move the DOM around with Vue

I don’t think this is a good idea, but I don’t know Vue very well. Maybe it’s got some tricks up its sleeve.

Future solution: reading-flow: grid-order

Chromium-based browsers (version 137+) now have the reading-flow and reading-order CSS properties. These allow users to augment the reading/focus order. The grid-order value for the reading-flow property would make the visual and focus order match for this layout.

It is possible to leverage this today as a progressive enhancement using @supports (reading-flow: grid-order) then setting the grid template how it currently is and reading-flow: grid-order.

Keep in mind that the majority of mobile screen reader users are likely using VoiceOver on iOS if the stats are correct.

Metadata

Metadata

Assignees

No one assigned

    Labels

    a11yRelated to accessibility and inclusion

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions