axe-core tabindex error tabindex greater than zero failed
Fixes the axe-core tabindex violation: elements with tabindex greater than zero. Use when axe flags 'elements should not have tabindex greater than zero'. Not for missing focus on custom widgets (use tabindex 0 or -1), and not for roving-tabindex patterns (those use 0 and -1 only).
axe-core tabindex error tabindex greater than zero failed - how to fix it
TL;DR
Positive tabindex values hijack the tab order: the browser tabs through tabindex="1", "2", "3" first, then everything else in DOM order, which scrambles navigation for keyboard and screen reader users. Remove every positive tabindex and put the elements in logical DOM order instead. Use tabindex="0" to add a custom widget to the natural order and tabindex="-1" for programmatic focus only.
Positive tabindex is sometimes introduced by drag-and-drop page builders or WYSIWYG editors that assign tab order automatically. If your CMS emits tabindex="1" through "47", the fix belongs in the builder's output template, not in per-page patches. One template fix clears every page.
The verbatim error looks like this:
axe-core 4.x
Rule: tabindex (serious)
"Elements should not have tabindex greater than zero"
Element: [button tabindex="3"]Submit[/button]
Positive tabindex disrupts natural tab order.Steps
- Find every positive tabindex in the codebase:
npx @axe-core/cli https://example.com/ --rules tabindexExpected: each element with tabindex greater than 0 is listed.
- For each one, ask why it was added. The usual reasons are visual order differing from DOM order, or an attempt to 'prioritize' a control. Both are fixed by reordering the DOM.
- Remove the positive values and reorder the markup so DOM order matches visual and logical order:
[button]Submit[/button]
(no tabindex attribute at all)For custom widgets that need focus: tabindex="0". For targets of programmatic focus (skip targets, modal containers): tabindex="-1".
- Re-run the focused scan. Expected output: 0 violations for the tabindex rule.
- Keyboard-test the full page top to bottom and confirm the tab order is logical and matches the visual layout.
- Find the source of the positive values. Search templates and components:
rg 'tabindex="[1-9]' src/ templates/ | head -20Shared components and CMS output templates are the usual suspects.
- After removing them, verify the visual order matches the DOM order. If a positive tabindex was compensating for a CSS layout that visually reorders content (flex row-reverse, absolute positioning), fix the layout so DOM order is the logical order. CSS order properties create the same confusion for screen readers as positive tabindex does.
When to use
- Axe flags the tabindex rule on elements with tabindex 1 or higher.
- Tab order jumps around the page unpredictably.
- A legacy template uses tabindex to force a 'priority' order.
When not to use
- A custom widget needs to be focusable (use tabindex="0", which is in the natural order).
- You need programmatic focus without a tab stop (use tabindex="-1").
- You are implementing roving tabindex in a toolbar or listbox (that pattern uses only 0 and -1 by design).
Why it happens
Positive tabindex creates a separate, invisible tab sequence that runs before the natural DOM order. Authors inevitably get it wrong as pages change, and users cannot predict where focus will go next. DOM order is the single source of truth for tab order; when visual order must differ, CSS (flex order, grid) should match the DOM, not tabindex.
tabindex was designed in an era before CSS could reorder layout, when authors needed manual control over focus sequence. Modern CSS made manual control unnecessary and harmful: the browser already tabs in DOM order, which is also the order screen readers use. Positive tabindex now survives mainly in generated code and in cargo-culted snippets.
Edge cases
- tabindex="0" on too many custom elements creates tab-stop fatigue. Prefer native elements (button, a, input) which are focusable without any tabindex.
- Negative tabindex values other than -1 (like -2) behave as -1 in practice but confuse readers. Use exactly -1.
- Third-party widgets sometimes ship positive tabindex. Override or wrap them; do not copy the pattern.
- After removing positive tabindex, re-check focus-order-semantics: the natural order must still be meaningful.
- tabindex="0" on a [div] that acts as a widget is correct but adds a tab stop. Prefer native elements to keep tab stops minimal.
- Modal dialogs should trap focus within the dialog while open. That trap uses tabindex="-1" and focus management, never positive values.
- Browser extensions and third-party widgets can inject positive tabindex. Scope your audit to your code, and report vendor issues upstream.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst89o96exEJa14Y9EVrNF9w