light, inspiration, solution, idea, innovation,Google Sheets iconSwift icon
Published at
Updated at
Reading time
2min
This post is part of my Today I learned series in which I share all my web development learnings.

Focus handling is essential for accessible and well-functioning interfaces. When building complex interfaces with custom UI components or custom elements, you should check if folks can interact with the components only with the keyboard.

The tabindex HTML element attribute plays a significant role when you implement custom focus handling.

Let's check what attribute values you can define:

<!-- 
  tabindex="0" -> element should be focusable 
  in sequential keyboard navigation
-->
<component-a tabindex="0">Foo</component-a>

<!-- 
  tabindex="-1" -> element should be focusable, 
  but should not be reachable 
  via sequential keyboard navigation
-->
<component-b tabindex="-1">Bar</component-b>

You can also set positive tabindex numbers, but it's considered to be an anti-pattern. Positive numbers affect the natural focus order of the document elements and messing with this order usually harms more than it helps.

With the tabindex attribute, you can control if custom elements can be focusable and if they should be reachable with keyboard navigation. This is useful when you want to lead the focus to elements when users are "tabbing around".

So, what's a "roving tabindex" then?

"Roving tabindex" is a technique to maintain a component's focus state. Think of a group of custom radio elements; what should be the behavior when you "tab" into this group? I'm expecting the following:

- tab into         👉🏻 the active radio gets focus
- left/right arrow 👉🏻 another radio element get focus
- another tab      👉🏻 focus goes out of the radio group
- tab back in      👉🏻 the last active radio get focus

This behavior can not be achieved when all elements are focusable and follow the natural focus order. tabindex=0 and tabindex=-1 can help out here. Set tabindex="0" on the selected radio, so that it receives focus when the user tabs into the component. Define tabindex="-1" on all the other elements, so that they are still focusable programmatically but are not reachable by the keyboard.

Then, write a little bit of JavaScript and toggle the states in case another elements becomes active after keyboard interaction such as pressing the arrow keys.

<radio-group>
  <custom-radio tabindex="-1">1</custom-radio>
  <custom-radio tabindex="-1">2</custom-radio>
  <custom-radio tabindex="0">3</custom-radio>
  <custom-radio tabindex="-1">4</custom-radio>
  <custom-radio tabindex="-1">5</custom-radio>
</radio-group>

This technique of toggling the tabindex attribute is called "roving tabindex". I discovered that Rob Dodson even recorded a whole a11ycast episode on this topic. Make sure to check this one out.

And that's it for today, happy tabbing! ;)

Related Topics

Related Articles