accessibility
Tabindex best practices
Flags positive tabindex values and overuse of tabindex="-1". Positive values break DOM order; only 0 and -1 belong in production markup.
What this check does
Scans all elements with a tabindex attribute and reports two issues:
- Positive values (
tabindex="1","2", etc.) — these override the natural DOM tab order and almost always cause focus bugs. - Overuse of
tabindex="-1"on elements that should be keyboard-reachable — a common mistake on custom buttons and links.
The check also notes when tabindex is applied to elements that are natively focusable (<a href>, <button>, <input>), where it’s usually redundant.
Why it matters
A positive tabindex creates a parallel keyboard universe. The browser tabs through all positive values first — in numeric order — then falls back to DOM order for everything else. Add tabindex="1" to one element and you’ve just promoted it ahead of every link, button, and form field on the page. Add it to ten elements and you’ve manually re-numbered the tab order, which means every future edit (a new menu item, a swapped form field) silently breaks the sequence.
WCAG 2.4.3 (Focus Order) requires that focus moves in an order that preserves meaning and operability. Visual order should match tab order. Positive tabindex almost always violates this — keyboard users and screen-reader users land in surprising places, lose context, and abandon the flow.
tabindex="-1" has the opposite problem when misused: it removes an element from the tab sequence entirely. Apply it to a button and keyboard users cannot reach it.
How to fix it
Rule of thumb: in normal application code, the only acceptable values are tabindex="0" and tabindex="-1", and you only need them on non-native elements.
When tabindex="0" is right
A custom interactive widget that the browser doesn’t focus by default:
<!-- Custom tab in a tablist -->
<div role="tab" tabindex="0" aria-selected="true">Overview</div>
<!-- Card that opens a modal on click -->
<div role="button" tabindex="0" onclick="openModal()">
View details
</div>
Prefer a real <button> whenever you can — it gives you focus, Enter/Space activation, and screen-reader semantics for free. Reach for tabindex="0" only when the native element won’t do.
When tabindex="-1" is right
A programmatic focus target — an element you’ll focus via JavaScript but that shouldn’t appear in the tab sequence:
<!-- Error summary, focused on form submit -->
<div id="errors" tabindex="-1" role="alert">
3 fields need attention
</div>
<!-- Modal container, focused on open -->
<div id="modal" tabindex="-1" role="dialog" aria-modal="true">
...
</div>
<!-- Skip-link target (main content) -->
<main id="main" tabindex="-1">...</main>
Then in JS:
document.getElementById("errors").focus();
The -1 makes .focus() work without inserting the element into Tab order.
When positive tabindex is right
Almost never. The single defensible case is integrating with a legacy form where you cannot reorder the DOM and the visual order requires re-sequencing — and even then, fixing the DOM is the right answer.
Lint it in CI
Catch this before review:
bun add -D eslint-plugin-jsx-a11y
// eslint.config.js
import jsxA11y from "eslint-plugin-jsx-a11y";
export default [{
plugins: { "jsx-a11y": jsxA11y },
rules: {
"jsx-a11y/tabindex-no-positive": "error",
"jsx-a11y/no-noninteractive-tabindex": "warn",
},
}];
For vanilla HTML / template repos, axe-core will catch the same issue at audit time — see Lighthouse accessibility failures.
Fix a page that already has positive tabindex
- Remove every
tabindex="1+"attribute. - Tab through the page with the keyboard. Note any surprising jumps.
- Reorder the DOM so the natural sequence matches the visual one. Use CSS (
order, grid placement) only for visual repositioning that doesn’t affect reading order. - Re-run the audit.
Frequently asked questions
Can I use tabindex="0" to make a <div> clickable?
You can, but adding role="button" and Enter/Space handlers is half the work — a real <button> is one element with everything wired up. The tabindex="0" + role="button" pattern is reserved for cases where you need a non-button element (e.g., a treeitem, a tab) that the spec doesn’t include as natively focusable.
Does tabindex="-1" hide the element from screen readers?
No. tabindex="-1" only removes the element from sequential keyboard navigation. Screen readers can still reach it via their own navigation (headings, landmarks, virtual cursor). To hide from assistive tech, use aria-hidden="true" — but never on an interactive element.
What about roving tabindex inside a menu or grid?
That’s the right pattern for composite widgets (menus, tablists, grids). Exactly one item has tabindex="0", the rest have tabindex="-1", and arrow keys move focus by toggling the values. The check accounts for this: a single 0 plus many -1s inside the same role="menu" / role="tablist" / role="grid" is not flagged.
Sources
Last updated 2026-05-11