Using focus manipulation and trapping for accessibility

One golden rule in UI development is that you don’t mess with the user’s perspective, so you don’t move focus, scroll the page or move the cursor without the user telling you to. However, in terms of accessibility, we found that it’s sometimes better to break that rule.

Key takeaways

  • When a user clicks on a link that, for example, displays a menu or a pop-up, navigating the content can be challenging if they are using a keyboard rather than a mouse or touchscreen.
  • People who use a keyboard or assistive technology may find it easier to navigate if their focal point is being moved by focus manipulation.
  • In addition to focus manipulation, focus trapping increases the accessibility of elements like pop ups by restricting the user's focus and prevents them from getting lost on the site.
This article is written by our frontend developer Koen Lageveen

Navigating with a keyboard

Not everyone uses a mouse or a touchscreen to interact with a website, but use the keyboard instead. That can be for various reasons. For instance, the user may have a permanent limitation in their ability to use a mouse accurately. Or it can be temporary, for example by having a hand or arm in bandages or a cast. In addition, users that have a visual impairment typically don’t use the mouse cursor either, but use the keyboard or assistive technology that behaves similarly to a keyboard.

In this situation, the user is able to move around on the website freely, moving from one link or a button to another, primarily by hitting the tab key. The focus point marks the user’s current position on the website, usually highlighted in blue. Some of those links will open a new page, and the user will typically start over reading and navigating the content, just like you would with a mouse but by hitting keys on the keyboard.

But what happens when one of those links does something different, like opening a menu or triggering a pop up, without opening a new page? This is where we need to do some manipulation of the focus point, and apply what is called ‘focus trapping’ in order to create an accessible user interface for people who use their keyboard for navigation.

Focus manipulation

Let’s say you use a keyboard for navigation and add something to your cart. We show that you’ve added the new item in your cart via a pop up message. If we did nothing here, your focus point would still be on the ‘add to cart’ button. Since you’re using the keyboard to navigate, you would have to hit the tab key quite often to get from that button to the newly opened pop up. This would be especially disorientating for blind users, because they do not see the pop up appear.

So instead of leaving the user’s perspective for what it is, we move their focus point to the pop up to help them navigate a bit more easily. Now you can hit the tab key to go to the first button, or use the escape key to close the pop up. This makes pop ups so much easier to use for anyone who needs to navigate with a keyboard or who uses assistive technology.

Focus trapping

Focus trapping is an extra step we take, with which we take the accessibility for things like the pop up to the next level. When you hit the tab key, your focus point will move from one focusable element to the next. If we don’t do anything here, the entire website that is behind the pop up is still focusable. Meaning that if you’re browsing around, you might actually accidentally leave the pop up and get lost.

That’s why we need to have focus trapping here. This means that we first have to make a list of things you could focus on. When you get to the end of that list (the final button or link in the pop up that is) we move your focus back to the first thing you can focus on. In this way you can’t get lost.

The code for focus manipulation and trapping for pop ups is surprisingly simple, and you can find the code in Github as an ES Module, written in TypeScript. Now you can easily incorporate this into your own projects. Just ‘setTrap()’ when the pop up opens, and ‘releaseTrap()’ when it closes.