Accessibility

tabindex: the only correct use

tabindex 0 belongs on a custom widget with a role and keyboard handling; -1 is for script focus. Positive values reorder the tab sequence and break it.

Key points

  • tabindex="-1" removes an element from the tab sequence but keeps it focusable from script: that is the most common legitimate use.
  • A positive value places the element ahead of everything with tabindex 0, in ascending order, and breaks criterion 2.4.3.
  • tabindex creates neither semantics nor keyboard support: on a div you also need a role and Enter, Space and arrow handling.

The only correct uses of tabindex are 0 on a custom widget that also gets a role and keyboard handling, and -1 to move focus from script. Positive values help no component: they reorder the tab sequence and break it.

0 and -1 are not the same thing

tabindex="0" puts the element into the document's natural order, at the point where it sits in the DOM. tabindex="-1" makes it focusable only from script: it stays out of the Tab sequence, yet element.focus() reaches it. The first makes a widget you built operable; the second moves focus where the code decides.

Why positive values are always a mistake

A positive value changes the sequence: first every element with tabindex="1", then those with 2, and only at the end those with 0 or no attribute, in DOM order. Criterion 2.4.3 asks that focus order preserve meaning and operability; an order that jumps from the header to the footer and back again breaks it. Maintenance is the worse part: every new element can force a full renumber.

The legitimate case: a focus target

A skip link only works if the target can take focus. On a <main> without tabindex="-1" the browser scrolls but focus stays on the link, and the next Tab starts from the top again. The same holds for a modal container and for the error summary after a form submit.

Skip to content


  Orders



  document.querySelector('.skip-link').addEventListener('click', (event) => {
    event.preventDefault();
    document.querySelector('#main').focus();
  });

Roving tabindex in composite widgets

A tablist, a menu or a radiogroup takes one tab stop: the active item has tabindex="0" and the others -1. The arrow keys move focus and update state, so aria-selected or aria-checked change together with the tabindex value. Without this pattern criterion 2.1.1 stays uncovered: the widget is reachable but not usable.

How to check it

The manual test is short: press Tab from the top of the page and count the stops; if focus jumps ahead and then back, a positive value is in the DOM. Axe has a dedicated rule and its cli installs from npm.

npx --yes @axe-core/cli https://example.com --tags wcag2a,wcag2aa

The command returns violations with selector and criterion. The positive tabindex rule is one a tool genuinely catches: the rest, meaning whether the widget answers the arrow keys, only a keyboard check tells you.

The EneUI catalogue

A verified record is Shadcn's navigation-menu, with an MIT licence declared upstream: keyboard navigation and focus handling come from the primitive, not from a hand-written tabindex. The other cases are in menu blocks and accessibility. Checked on 1 November 2026 against 111,985 components.

The limitation

tabindex changes only what Tab can reach. It does not repair a wrong DOM order, it creates no role, and it does not steer a screen reader in browse mode, where movement follows elements rather than focus. If the visual order is right and the DOM order is not, the problem is layout and CSS fixes it, not attributes.

Where to continue

Visible focus is covered in the invisible focus ring; the full checklist is in accessible React components. The mirror-image mistake, a name that contradicts the text, is in aria-label on buttons.

Frequently asked questions

Can I put tabindex on a div?

Only with a role and keyboard handling. A div with tabindex="0" and nothing else is a control you can reach but not operate, and it breaks 4.1.2.

Why is tabindex="1" a problem?

Because positive values come before every zero, in ascending order. One new element forces a renumber, and the visual order no longer matches.

When do I need tabindex="-1"?

When script must move focus onto a container: a skip link target, the first element of a modal, the error summary after a submit.

Does tabindex="-1" remove the focus ring?

No, the ring stays unless your styles suppress it. Never use outline: none to hide it: keyboard users lose their only position cue.

What is roving tabindex?

A pattern for tabs, menus and radiogroups: the active item has tabindex="0", the others tabindex="-1", and the arrow keys move focus and state. One tab stop for the whole widget.