Cross-Browser Tabindex Woes

The HTML 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 basics

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

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

Sequential tabindex

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.

Test details

According to the HTML specification, tabindex is a valid attribute on the following elements:

  • a
  • area
  • button
  • input
  • object
  • select
  • textarea

Because I love and respect myself, I did not test area or object. I did, however, add examples of each of the other elements, including three types of inputs, as well as div and 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 input elements, textarea, select, div, and span respect 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.

Differences in 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 div with 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 a, 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 div and span have more consistent support for tabindex than interactive ones like a and 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.