loading...

Managing focus for accessibility

robdodson profile image Rob Dodson ใƒป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 dev.toโ€”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:

<nav>
  <a href="/">Home</a>
  <a href="/cart">Shopping Cart</a>
  <a href="/settings">Settings</a>
</nav>
<main>
  <!-- some content -->
</main>

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.

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

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 ๐Ÿ“บ

โœŒ๏ธ

Posted on by:

robdodson profile

Rob Dodson

@robdodson

I work on the Chrome developer relations team, with a specific focus on accessibility. My job is to help folks build a better web ๐Ÿ˜ƒ

Discussion

markdown guide
 

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...

 

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.

developers.google.com/web/fundamen...

 

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

 

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?

 

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 (developers.google.com/web/updates/...) 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.

 

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.

 

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!

 

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

 

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.