When the DOM Tab Order Isn't Enough

The default tab order, determined by element position in the DOM, works well in most cases. But when you can't rearrange the HTML to achieve the right flow, the tabindex attribute gives you explicit control over focus behavior.

Technically, tabindex can go on any element, though it's rarely useful on non-interactive ones. It accepts integer values that serve three distinct purposes:

  • tabindex="0": Adds the element to the natural tab order. The user can reach it with the Tab key, and developers can focus it programmatically via its focus() method.
  • tabindex="-1": Pulls the element out of the natural tab order. It's not reachable by keyboard, but you can still call focus() on it.
  • tabindex="5" (or any positive integer): Puts the element at the front of the tab order. With several positive values, focus moves from the lowest to the highest value, independent of their DOM position.

If you must adjust the order manually, reserve tabindex for custom interactive widgets—buttons, tabs, dropdowns, text fields. Adding it to static content like headings or images rarely helps. Screen reader users can perceive important content without moving focus to it, so only add tabindex where user input is expected.

Managing Focus Across Page Sections

Single-page interfaces often show and hide content sections without a page reload. When a click changes the visible panel, the user's focus may remain on the old content, breaking their sense of location.

The fix is a technique called managing focus. After a navigation action, find the newly visible content area, set its tabindex to -1, and call its focus() method. That keeps the element out of the natural tab order while letting you move the user's attention to where the content actually changed.

Roving Focus Inside Custom Components

Native controls like select do more than accept a single focus stop. Once focused, arrow keys reveal and select additional options. If you build a custom select or a set of radio buttons, you need to replicate that keyboard behavior.

The ARIA Authoring Practices Guide documents the expected keyboard support for common component types. In many cases, you'll need to support the arrow key navigation that built-in controls offer.

Radio groups provide a good example. The typical implementation relies on roving tabindex, which keeps exactly one child in the tab order at a time:

  • Set tabindex="-1" on every child except the currently active one.
  • Set tabindex="0" on the active radio button.
  • Listen for arrow key events on the group. When one arrives, remove focus from the old child with tabindex="-1", promote the new child with tabindex="0", and call its focus() method.

As the user moves past the last (or first) child, the focus wraps back around to the opposite end of the group.

Keyboard Traps and Modals

Manual focus management can backfire. A common failure is a widget that captures the Tab key but never lets the user leave—a keyboard trap. WCAG's Section 2.1.2 is explicit: keyboard focus must never be locked to a single page element.

Modals are the one intentional exception, but flagrant tabindex manipulation is still the wrong tool. The native <dialog> element handles this safely; it's inert by default, so content behind it can't receive clicks or keyboard focus while the dialog is open. Applying the inert attribute to non-dialog content achieves the same effect, letting the user concentrate on the required selection without trapping them.