DEV Community


Managing focus for accessibility

Rob Dodson
I work on the Chrome developer relations team, with a specific focus on accessibility. My job is to help folks build a better web 😃
・2 min read

If I'm building a single page web app, what should happen to focus when the user clicks some navigation?

This is a video I shot a while back, but the question comes up so often I wanted to re-share here on—plus it's a good excuse to write my first post 😁

Typically in a single page app you'll have a structure that looks roughly something like this:

  <a href="/">Home</a>
  <a href="/cart">Shopping Cart</a>
  <a href="/settings">Settings</a>
  <!-- some content -->
Enter fullscreen mode Exit fullscreen mode

Because it's a single page app, clicking on a link won't do a hard refresh. Instead, it will trigger a route change which usually uses a bit of AJAX to fetch some more data and populate the <main> content area.

For a sighted user this is all good—but what if I'm a non-sighted user navigating with a screen reader? I may not know that the new content has been added to the page (maybe I was expecting a hard refresh) and there's no indication that I should navigate back to the <main> area.

One easy way to improve this experience is to find a good heading in the newly loaded content and direct focus to it. The easiest way to pull this off is to give the heading a tabindex of -1 and call its focus() method.

  <h2 tabindex="-1">Welcome to your shopping cart</h2>
  // Assuming this gets called every time new content loads...
  function onNewPage() {
    var heading = document.querySelector('h2');
    // You can also update the page title :)
    document.title = heading.textContent;
Enter fullscreen mode Exit fullscreen mode

A screen reader will then announce the new heading, as well as the main landmark area that it's contained within. Note that you probably don't want to do this focus management when a user first arrives at your site—you would only want to do it for subsequent navigation, like when they click a link.

Hope you enjoyed this post! It's an easy trick that could make a big difference for some of your users. And if you're interested in more a11y screencasts check out my playlist over on the YouTubes 📺


Discussion (9)

nickytonline profile image
Nick Taylor (he/him) • Edited

Begin dev confessions...

I must admit, I am weak when it comes to ally skills aside from some knowledge of aria-* attributes. I must also admit that most places I've worked at to the best of my knowledge never even had ally on the radar and I feel like I am probably not the only dev that has had this experience.

End dev confessions...

robdodson profile image
Rob Dodson Author

Hey that's ok! Folks have to start somewhere :)

I know it can be tough to get an organization motivated to work on it, and the topic area itself can feel pretty arcane at times. If it's any help, I put together a doc and a video for last year's Google I/O which covers what various folks on the team can do to improve the accessibility of their site.

nickytonline profile image
Nick Taylor (he/him)

Thanks for sharing Rob. Looking forward to reading/viewing more content from you.

bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Hey Rob thanks for the tip!

MDN has me telling anyone who will listen that non-interactive elements shouldn't have a tabindex. Are we just going to have to say that SPAs are an exception to the rule?

robdodson profile image
Rob Dodson Author

Usually that's correct--you should only give an element tabindex if a user can interact with it or provide it some input.

But for the specific case of managing focus I've often seen folks use this technique because there is no alternative way to move the users focus start point. Browsers actually have an internal API that they use to move the focus start point ( but it's not exposed to developers as a JavaScript API. The tabindex + focus() trick I showed is a sort of near term hack to achieve a similar effect.

One of the things I really want to work on next is exposing the focus start point API so this trick becomes unnecessary.

themesurgeon profile image
John Teague • Edited

I love these tips. I spent years trying to get employers to take it seriously, but it mostly ended up cycling in a pattern of brief starts and fade outs. One nice thing about starting a business was I got to make it a top priority. Thanks for keeping a11y in the forefront.

theutz profile image
Michael Utz

Great post. I think if I'm going to start learning a11y, it's going to come in bite-sized chunks like this. Thanks for your work!

soleo profile image
Xinjiang Shao

It works most of the time , but in iOS sometimes it just ignores .focus() no matter what you do. Any suggestions?

robdodson profile image
Rob Dodson Author

I think I've seen bugs related to this before. If you have an example that you can put into a jsbin I can try to send it over to my friends on the Safari team.