(This post was originally published at Up Your A11y: Handling Focus on Route Change in React)
Any client-side route rendering can result in confusion and loss of context for assistive technology users. In this post we'll very briefly get to grips with:
- Understanding some of the focus problems caused by client-side rendering
- Implementing a simple technique to restore context for assistive technology users and make sure that more people can engage with your content
Server side vs Client side rendering
At its simplest level, server-side rendering means that when you navigate to a new route, the server is contacted to request the page to display, and a whole new page is presented in the browser.
Client-side rendering, on the other hand, means that both 'examplesite.com' and "examplesite.com/page2" are actually the same page (index.html), but the client app decides what content to drop into that single page at runtime.
In reality, there is a bit more to it than that, especially with new server-side rendering techniques, but the key to understanding focus management in React is to understand that when a user clicks a link to go to another route in your app, the DOM is manipulated at runtime, and the content of your single page is altered. Your user never actually "leaves" the page.
There are a few accessibility concerns this causes, one of which being the way focus is handled when that route change takes place.
A quick comparison of a simple link click
Imagine the following scenario - as a screen reader user, you read a link to another page within the same web app. You click the link using the keyboard commands. What do you expect to happen?
In "server-side rendering" land, what would happen is:
- The screen reader informs you that you pressed the link
- An entirely new page is loaded into the browser
- The focus of the page is reset
- The new page is announced
But as we know, with client-side rendering like in React, we wonβt receive a new page. If focus is not explicitly handled, a more likely chain of events is:
- The screen reader informs you that you pressed the link
- The new content is fetched and populated in the UI
- Your screen reader does not announce anything to you about the new content
- The focus remains on the link on the first page, even though it is no longer visible
Try to imagine just how disorientating this would be for a user with a visual impairment. How can they know where to begin on this new page of content?
The current point of focus may well be in the middle of the page, and nowhere near the primary content you want them to read. When they try to begin reading the new content, they might not readily identify it as useful, or they may be frustrated by the lack of context. In either case, it is likely they will give up and leave your app.
Potential solutions
There are a few ways to attempt to solve this problem, all involving manually manipulating the focus on the page when the new content loads. The question then is: where do we set the focus when the new 'page' loads?
Recently, GatsbyJS posted an interesting article summarising some user-testing of these techniques. I recommended reading their post in full, but spoiler alert:
Focusing on a heading was found to be the best experience as it would save time and make it clear what happened
A very simple solution
To continue the simple link-click example from above - the behaviour found most desirable in Gatsby's user tests is to ensure the following sequence of events:
- You click the link, and screen reader confirms that you pressed it
- The new content is fetched and populated in the UI
- Once the new content is loaded, focus is immediately placed on the new content's 'h1' element
- The content of the 'h1' is read out by the screen reader
This helps restore context in two key ways:
- The 'h1' is likely to be at the top of the page, so the keyboard focus position is reset to a more conventional position, instead of potentially floating around in the middle of the page
- The 'h1' should already contain the most relevant description of the new page, and what the user can expect to find on it (it's the primary header of the page, after all!). Announcing it immediately orients the user to the new content.
Implementing the solution
Implementing this behaviour is very straightforward, and only requires three basic steps:
- Insert the h1 element at the beginning of the tabbing order, adding a ref to it
- In componentDidMount() focus that h1 using the ref you created
- Disable the default focus highlight on the h1 element, to prevent the focus being visible other than to screen readers
A very basic example implementation of a "Focusable Header" component:
class FocusableHeader extends React.Component {
headingRef = React.createRef()
componentDidMount() {
this.headingRef.current.focus()
}
render() {
return (
<h1
ref={this.headingRef}
className="focusable-header"
tabIndex="-1" >
I'm a focusable header!
</h1>
)
}
}
export default FocusableHeader
And the related CSS to disable the visible focus style for this specific type of header:
.focusable-header:focus {
outline: none;
}
And that's it!
In a few easy steps, the focus on route change can be handled, and your content can be easily consumed by a wider range of users.
Bear in mind however that inserting items into the tabbing order and disabling focus highlights should only be done with extreme caution and careful consideration; I only recommend this based on the user research in this specific use-case.
If you'd like to see a version of this post with in-app examples of the route changes so you can test drive both the initial approach and the example solution, head on over to Up Your A11y where you'll find just that!
Did you find this post useful? Please consider buying me a coffee so I can keep making content π