tabindex attribute is a useful tool for accessibility when used properly. Unfortunately for a11y-minded developers, most browsers don’t handle the attribute consistently. This can be challenging to deal with.
tabindex is an HTML attribute that allows you manipulate the tab order of elements. By default, pressing the Tab key on a web page will set browser focus on interactive page elements in the order that they appear in the document. Once focused, you can use the keyboard to interact with elements—activating a link, submitting a button, or entering information into an input.
Making elements focusable
tabindex="0" to an element makes it keyboard focusable, whether it is an interactive HTML element or not. This can be useful for building custom form controls or application components. but can also be a potential accessibility hazard if these elements don’t have correct WAI-ARIA attributes to make their usage clear to people with screen readers.
tabindex values greater than 0 set elements to particular positions in the page’s tab order.
tabindex="1" will make an element the first item to gain focus when tabbing through the page, followed by any higher numbered tab indices, followed by any other keyboard focusable elements (including those with
tabindex="0"). If multiple elements have the same
tabindex, those items will be ordered relative to each other. Any items with
tabindex="1" will be first in the order that they appear in the document, followed by items with
tabindex="2", and so on.
This behavior is extremely powerful, but in practice isn’t as useful as you might think. It can be difficult to manage sequential tab indices on a large or dynamic page and ensure that everything stays in the order you want, and setting a custom tab order can make it difficult for keyboard users to move around your page, as their browser focus may not go where they expect.
Removing elements from tab order
The last type of valid value for
tabindex is negative values. Adding
tabindex="-1" to an element removes it from the document’s tab order completely, preventing keyboard users from focusing on it. This is pretty dangerous and is usually not a good idea unless you have a good reason. An element shouldn’t have tab behavior disabled if users can interact with it.
Good thing all browsers handle tabindex the same way
In building keyboard accessible interfaces, I’ve found that setting
tabindex="0" on an element doesn’t always get the job done. After a few frustrating experiences trying to figure out why
tabindex doesn’t always work the way you might expect, I put together a test suite to figure out what was going on.
My test document is a table with examples of the primary types of elements that
tabindex is valid on, as well as a few that it is not valid on (according to the specification), with various values set. The behavior for various elements varied considerably between browsers and operating systems.
According to the HTML specification,
tabindex is a valid attribute on the following elements:
Because I love and respect myself, I did not test
object. I did, however, add examples of each of the other elements, including three types of inputs, as well as
span. You can try the test suite out here.
So what happens?
Some browsers respect
tabindex all of the time. Others respect it for certain elements, or with certain modifier keys held down while tabbing, or with certain system preferences enabled on your computer, or for certain elements under certain conditions. Cool, right? To be fair, this situation has actually improved quite a bit since I looked at this last. Safari (as of OS X El Capitan) has shown the most improvement, going from being incomprehensible to making sense as long as you know the rules.
The good news first. Chrome, Opera, and Internet Explorer all accept their new tab orders with open arms. All interactive elements and elements with a non-negative
tabindex can be accessed by tabbing through the document. With the exception of radio buttons. We’ll come back to that later.
Firefox works similarly, with the exception that on OS X
a elements aren’t tab-accessible by default, with or without
tabindex. You can access
a elements with the keyboard in Firefox two ways. You can hold down the option key while pressing tab, or you can change your system preferences. Firefox respects an option called “Full Keyboard Access” in your keyboard preferences, which controls which types of controls can be accessed with tab on your computer. If you toggle that option to “All Controls”, Firefox will focus on links like nobody’s business.
Safari is where things get weird. In Safari, text
tabindex by default, but buttons and radio and option inputs aren’t tab-accessible. If the user sets “Full Keyboard Access” to “All Controls” in their preferences or uses
option + tab, all elements will work as expected, same as Firefox.
The radio thing
Radio input support for
tabindex is… quirky. As a general rule, once a radio input in a group is selected, only the selected input is tab-accessible. From there, you can use the arrow keys to activate different inputs in the group. If no radio inputs in a group are selected, behavior varies between browsers.
In Chrome and Opera, the first radio input you attempt to tab to in a group is tabbable by default, meaning the first input in the group if you’re tabbing forward, and the last one in the group if you’re tabbing backward. All inputs with sequential
tabindex values are tab-accessible too, but setting
tabindex="0" won’t do anything at all.
Firefox behaves similarly, except that
tabindex="0" works as expected.
Safari behaves the same way as Chrome and Opera, except that you need to use
option + tab or set your preferences to access all controls by default to reach radio inputs.
Internet Explorer handles radio inputs in the same way as Chrome and Opera, except for some strange behavior with negative
tabindex values in IE8 and IE9. In those browsers, if a radio button is selected and has a negative
tabindex, it will still be tabbable. No other browsers behave this way.
Take It Away
The browser differences in default tabbing behavior, like depending on particular system preferences or keyboard modifiers, are annoying for developers unfamiliar with them, but shouldn’t be too big of a deal for your users in general. Hopefully, they’re familiar with their browser of choice by virtue of using it regularly, so they know how to reach various UI controls with their keyboards.
tabindex support are a little more difficult to deal with, particularly when building custom UI controls. As a general rule, it’s probably a good idea to try to match the tabbing behavior of your component to the type of native element that it most closely resembles. For example, if you’re building a custom
select element replacement, you might be best served by using a
tabindex="0", which will behave the same way as a normal
select element in the tab order.
If you need to work with sequential
tabindex values, first— try not to. If you absolutely have to, be aware that particular types of elements like
button, and non-text
input may not fit in the tab order the way that you expect them to, depending on the browser.
When in doubt, non-interactive elements like
span have more consistent support for
tabindex than interactive ones like
button, despite the fact that the attribute is technically not valid when used with them according to the specification. If an element absolutely needs to be in the document’s tab order cross-browser without requiring specific system preferences or keyboard modifiers,
div might be your best choice. Just be sure to add appropriate ARIA attributes and keyboard event handlers so that users can tell what the element is and interact with it when they get to it.