Modifying the DOM order with tabindex
The default tab order provided by the DOM position of native elements is
convenient, but there are times when you'll want to modify the tab order, and
physically moving elements in the HTML isn't always an optimal, or even a
feasible, solution. For 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 is not necessarily
useful on every element - and takes a range of integer values. Using
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 the Tab
key, and the element can be focused by calling
its focus()
method
<custom-button tabindex="0">Press Tab to Focus Me!</custom-button>
tabindex="-1"
: Removes an element from the natural tab order, but the element
can still be focused by calling its focus()
method
// TODO: DevSite - Code sample removed as it used inline event handlers
// TODO: DevSite - Code sample removed as it used inline event handlers
tabindex="5"
: Any tabindex greater than 0 jumps the 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.
<button>I should be first</button>
<button>And I should be second</button>
<button tabindex="5">But I jumped to the front!</button>
This is particularly true of non-input elements like headers, images, or article
titles. Adding tabindex
to those kinds of elements is counter-productive. If
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.
Don't worry about screen reader users missing important content because it
doesn't have a tabindex
. Even if the content is very important, like an image,
if it's not something the user can interact with, there's no reason to make it
focusable. Screen reader users can still understand the content of the image so
long as you provide proper alt
attribute support, which we'll cover shortly.
Managing focus at the page level
Here's a scenario where tabindex
is not only useful, but necessary. You might
be building a robust single page with different content sections, not all of
which are simultaneously visible. In this kind of page, clicking a navigation
link might change the visible content without doing a page refresh.
When this happens, you would probably identify the selected content area, give
it a tabindex
of -1 so that it doesn't appear in the natural tab order, and
call its focus
method. This technique, called managing focus, keeps the
user's perceived context in sync with the site's visual content.
Managing focus in components
Managing focus when you change something on the page is important, but sometimes you need to manage focus at the control level — for example, if you're building a custom component.
Consider the native select
element. It can receive basic focus but, once
there, you can use the arrow keys to expose additional functionality (the
selectable options). If you were building a custom select
element, you would
want to expose these same kinds of behaviors so that users who rely primarily on
the keyboard could still interact with your control.
<!-- Focus the element using Tab and use the up/down arrow keys to navigate -->
<select>
<option>Aisle seat</option>
<option>Window seat</option>
<option>No preference</option>
</select>
Knowing which keyboard behaviors to implement can be difficult, but there is a helpful document you can refer to. The Accessible Rich Internet Applications (ARIA) Authoring Practices guide lists types of components and what kinds of keyboard actions they support. We will cover ARIA in more detail later, but for now let's use the guide to help us add keyboard support to a new component.
Perhaps you're working on some new 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 kind of keyboard support they need, you would check the ARIA Authoring Practices guide. Section 2 contains a list of design patterns, and in that list is a characteristics table for radio groups, the existing component that most closely matches your new element.
As you can see in the table, 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'll 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 then 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, you will loop around and focus the first (or last) child again.
You can give the completed example a try down below. Inspect the element in the DevTools to observe the tabindex moving from one radio to the next.
// TODO: DevSite - Code sample removed as it used inline event handlers
You can view the complete source for this element over on GitHub.
Modals and keyboard traps
Sometimes when you're managing focus you can get into a situation you can't get out of. Consider 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 the Web AIM checklist addresses this issue, stating 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.
Oddly, there are times when this behavior is actually desirable, like in a modal window. Normally, when the modal is displayed, you don't want the user to access the content behind it. You might add an overlay to cover the page visually, but that doesn't stop keyboard focus from accidentally traveling outside the modal.

In instances like this you can implement a temporary keyboard trap to ensure that you trap focus only while the modal is displayed and then restore focus to the previously-focused item when the modal is closed.
There are some proposals on how to make this easier for developers, including the
<dialog>
element, but they don't yet have widespread browser support.See this MDN article for more information on
<dialog>
, and this modal example for more information on modal windows.
Consider a modal dialog represented by a div
that contains a few elements, and
another div
that represents a background overlay. Let's walk through the basic
steps needed to implement a temporary keyboard trap in this situation.
- Using
document.querySelector
, select the modal and overlay divs and store their references. - As the modal opens, store a reference to the element that was focused when the modal was opened so you can return focus to that element.
- Use a keydown listener to grab keys as they are pressed while the modal is open. You could also listen for a click on the background overlay, and close the modal if the user clicks it.
- Next, get the collection of focusable elements within the modal. The first and last focusable elements will act as "sentinels" to let you know when to loop focus forward or backward to stay inside the modal.
- Display the modal window and focus the first focusable element.
- As the user presses
Tab
orShift+Tab
, move focus forward or backward, looping at the last or first elements as appropriate. - If the user presses
Esc
, close the modal. This is very helpful because it allows the user to close the modal without searching for a specific close button, and it benefits even users who are using a mouse. - When the modal is closed, hide it and the background overlay, and restore focus to the previously-focused element saved earlier.
This procedure gives you a usable, non-frustrating modal window that everyone can use effectively.
For more details, you can examine this sample code, and view a live example from a completed page.