I’m working on a Single Page Application with some accessibility requirements on the horizon. However, the recent legal ruling against Domino’s requiring ADA conformance, some recent #hotdrama around a CSS-Tricks article, as well as a private conversation I had with an accessibility expert who mentioned SPAs were one of the biggest recurring failures he comes across, I decided to poke around a bit earlier than usual. The first thing I took a look at was keyboard-only and screenreader navigation.
I’m a big fan of knowing the expectations when it comes to accessibility. When you navigate on a server rendered page, the page is torn down by the browser and focus is reset to the top of the window. Once at the top of the window, a screenreader will read the new page <title>
(and URL sometimes) to give the user a frame of reference of where they are and confirmation they got to where they were going. Then the user begins tabbing through content, hopefully assisted by some helpful skip navigation links.
Single Page Apps are a bit different. Since we’re not relying on the native page load functionality, we’re now in the tricky business of managing focus ourselves. But don’t fret, there are essentially two solutions to help non-sighted or keyboard-only users get to where they’re going:
- Emulate a native page load and move focus to the top of the document.
- Move Focus on the content (heading or possibly form control).
The first solution is like a page load rewinding focus to the top, while the second solution is akin to a tab control where focus is pushed to the content. I like both of these solutions and here are some example demos from accessibility experts I trust:
- Scott Vinkle created a React app called TV-Db (Source on Github) that shows the first pattern
- Paul J. Adam has a demo of each as well as form control focussing as well
And if you’re like me, you just want Rob Dodson from A11ycasts to explain it to you in a video:
There’s some tabindex
and aria-labelledby
work happening beneath the hood in these examples, so unfortunately that makes it a bit more difficult than just focus management. While not as complex as something like a tab control, the number of expectations for a page navigation is increasing slightly.
From a framework standpoint, we haven’t quite reached “Accessible by Default” status across the board but good news is there are some smart people working on some initiatives to improve the default state of router-level navigation.
- Reach Router for React by Ryan Florence is an excellent choice for React and implements the second pattern focusing page content.
- Ember has an RFC to phase in the first pattern then the second pattern
- Vue Router is looking at the second pattern
This is anecdotal, but one thing I’ve noticed while browsing around is that often times sites do nothing (e.g. the React and VuePress docs sites). This isn’t inaccessible per se, because content is still Tab
-navigable, but the expectation contract is broken when you click a link and nothing happens if you’re a non-sighted user. I think where this is a bigger issue is inside giant sidebar subnavigation trees. Screenreader users have a few workarounds; they can navigate by headings, forms, or links but folks with limited mobility (e.g. MS, ALS), might hate-Tab
through your site.
I’d love for this to standardize a bit more. Maybe there’s a syntheticPageLoad
event or something in the browser or an extension to pushState
that rewinds the focus to the top of the window. For me and my brain, it makes sense that top-level page navigations abide by the first pattern and in-page subnavigations abide by the second pattern. But again, I am not an accessibilty expert.
In general, I was happy to find people working on these problems at the router level. If done right and widely adopted a lot of sites can get better quickly on the next npm install
. I do slightly worry (knowing what I know about accessibility) there’s no silver bullet on making this work easy, intuitive, and applicable in every situation. Often times in accessibility doing nothing is better than doing it the wrong way. It’s a curse. That’s why I’d love more dialog around working approaches so that frameworks being heavily relied on can make big improvements through small changes.
Now for my next question: If a piece of state changes and that update cascades to multiple components on the page… what’s the best way of notifying the user? A bunch of aria-live
regions littered throughout the page that you have to manage? Rewind the focus to the top of the page?