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