Modify DOM order with tabindex

Dave Gash
Dave Gash
Meggin Kearney
Meggin Kearney
Alexandra Klepper
Alexandra Klepper

The default tab order provided by the DOM position of semantic HTML elements is convenient, but there may be times you need to modify the tab order. Moving elements in the HTML is ideal, but might not be feasible. In these cases, you can use the tabindex HTML attribute to explicitly set an element's tab position.

Browser Support

  • 1
  • 12
  • 1.5
  • ≤4

Source

tabindex can be applied to any element, although it's not necessarily useful on every element, and takes a range of integer values. With tabindex, you can specify an explicit order for focusable page elements, insert an otherwise unfocusable element into the tab order, and remove elements from the tab order. For example:

tabindex="0": Inserts an element into the natural tab order. The element can be focused by pressing Tab, and the element can be focused by calling its focus() method.

tabindex="-1": Removes an element from the natural tab order, but the element can still be focused by calling its focus() method

tabindex="5": Any tabindex greater than 0 brings that element to the front of the natural tab order. If there are multiple elements with a tabindex greater than 0, the tab order starts from the lowest value that is greater than zero and works its way up. Using a tabindex greater than 0 is considered an anti-pattern.

This is particularly true for non-input elements like headers, images, or article titles. When possible, it's best to arrange your source code so the DOM sequence provides a logical tab order. If you do use tabindex, restrict it to custom interactive controls like buttons, tabs, dropdowns, and text fields; that is, elements the user might expect to provide input to.

Only add tabindex to content that is interactive. Even if content is important, such as a key image, screen reader users can understand it without adding focus.

Manage focus at the page level

Sometimes, tabindex is necessary for a seamless user experience. For example, if you build a robust single page with different content sections, where not all content is simultaneously visible. This could mean navigation links change the visible content, without a page refresh.

In this case, identify the selected content area and give it a tabindex of -1 and call its focus method. This ensures the content doesn't appear in the natural tab order. This technique, called managing focus, keeps the user's perceived context in sync with the site's visual content.

Manage focus in components

In some cases, you must also manage focus at the control level, such as with custom components.

For example, the select element can receive basic focus, but once there, you can use the arrow keys to expose additional selectable options. If you build a custom select element, it's important to replicate that behavior, so keyboard users can still interact with your control.

Knowing which keyboard behaviors to implement can be difficult. The Accessible Rich Internet Applications (ARIA) Authoring Practices guide lists types of components and what kinds of keyboard actions they support.

Perhaps you're working on Custom Elements that resemble a set of radio buttons, but with your unique take on appearance and behavior.

<radio-group>
    <radio-button>Water</radio-button>
    <radio-button>Coffee</radio-button>
    <radio-button>Tea</radio-button>
    <radio-button>Cola</radio-button>
    <radio-button>Ginger Ale</radio-button>
</radio-group>

To determine what keyboard support they need, check the ARIA Authoring Practices guide. Section 2 contains a list of design patterns, including characteristics table for radio groups, the existing component that most closely matches your new element.

One of the common keyboard behaviors that should be supported is the up/down/left/right arrow keys. To add this behavior to the new component, we use a technique called roving tabindex.

Roving tabindex works by setting tabindex to -1 for all children except the currently-active one.

<radio-group>
  <radio-button tabindex="0">Water</radio-button>
  <radio-button tabindex="-1">Coffee</radio-button>
  <radio-button tabindex="-1">Tea</radio-button>
  <radio-button tabindex="-1">Cola</radio-button>
  <radio-button tabindex="-1">Ginger Ale</radio-button>
</radio-group>

The component uses a keyboard event listener to determine which key the user presses; when this happens, it sets the previously focused child's tabindex to -1, sets the to-be-focused child's tabindex to 0, and calls the focus method on it.

<radio-group>
    <!-- Assuming the user pressed the down arrow, we'll focus the next available child -->
    <radio-button tabindex="-1">Water</radio-button>
    <radio-button tabindex="0">Coffee</radio-button> // call .focus() on this element
    <radio-button tabindex="-1">Tea</radio-button>
    <radio-button tabindex="-1">Cola</radio-button>
    <radio-button tabindex="-1">Ginger Ale</radio-button>
</radio-group>

When the user reaches the last (or first, depending on the direction they're moving the focus) child, focus loops back around to the first (or last) child.

Try the following example. Inspect the element in the DevTools to observe the tabindex moving from one radio to the next.

Modals and keyboard traps

It's best to avoid manually managing focus, as it can lead to complicated situations. For example, an autocomplete widget that tries to manage focus and captures the tab behavior, but prevents the user from leaving it until it's complete. This is called a keyboard trap, and it can be very frustrating for the user.

Section 2.1.2 of WCAG states that keyboard focus should never be locked or trapped at one particular page element. The user should be able to navigate to and from all page elements using only the keyboard.

The exception to this rule is modals. However, you should still avoid using tabindex when creating a modal. With inert, you can ensure that users can't accidentally interact with an element (an intentional keyboard trap). Use the <dialog> element, which is inert by default, to create a modal for users while blocking clicks and tabs outside of the modal. This allows the user to focus on a required selection.