no-noninteractive-tabindex
Configuration
Rule Details
Tab key navigation should be limited to elements on the page that can be
interacted with. A tabIndex of 0 (or any positive value) on a
non-interactive HTML element forces it into the keyboard tab order even
though there is nothing for the user to do once focus arrives — disrupting
expectations for both sighted keyboard users and assistive-technology users.
The rule fires on a JSX opening element when all of the following hold:
- The element has a
tabIndexprop whose value resolves to a usable non-negative integer (tabIndex={0},tabIndex="0",tabIndex={5}, …). - The resolved element name is in the HTML DOM set (custom React components are skipped — the rule does not know what low-level element they render).
- The element is not inherently interactive (e.g.
<button>,<a href>,<input>,<textarea>,<select>). - The
roleattribute, when present and statically a literal string, is not in the interactive (widget) ARIA role set.
A tabIndex of -1 (negative) is always allowed — that is the standard
"focusable via JavaScript but not via Tab" pattern.
Examples of incorrect code for this rule:
Examples of correct code for this rule:
Rule Options
tags
Type: string[]. Default: not set.
A list of element names that should be exempt from the rule. Useful when a
codebase has a vetted convention for putting tabIndex on a specific
non-interactive tag.
Examples of correct code with { "tags": ["div"] }:
roles
Type: string[]. Default: not set. The upstream recommended preset sets
this to ["tabpanel"].
A list of literal ARIA role values that should be exempt — the rule
short-circuits when the element's role attribute resolves to one of these
strings.
Examples of correct code with { "roles": ["tabpanel"] }:
allowExpressionValues
Type: boolean. Default: false. The upstream recommended preset sets
this to true.
When true, an element whose role attribute is a non-literal expression
(e.g. role={ROLE_BUTTON}, role={isButton ? "button" : "link"}) is
exempt — the rule cannot statically determine whether the role is
interactive, and the option opts into trusting the developer.
Examples of correct code with { "allowExpressionValues": true }: