jsx-a11y/no-noninteractive-tabindex

Disallow tabIndex of 0 or more on non-interactive elements

Also called jsx_a11y/no-noninteractive-tabindex

Every element with tabIndex={0} is one more stop for the Tab key, and a keyboard user expects to be able to do something at each stop. A stop on plain content gives them nothing to do and puts the real controls further away.

tabIndex={-1} isn't reported, because it lets your code call focus() on the element without adding a stop. Interactive elements, and elements with an interactive role such as button, can have any tabIndex. Components aren't checked.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"jsx-a11y/no-noninteractive-tabindex" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"jsx-a11y/no-noninteractive-tabindex" = ["error", {
  tags = [], # list of strings
  roles = ["tabpanel"],
  allowExpressionValues = true,
}]

Examples#

example.jsx2 problems
const summary = <article tabIndex={0}>{post.summary}</article>;
const box = <div tabIndex="0" />;
example.jsxNo problems
const summary = <article>{post.summary}</article>;
const heading = <h1 tabIndex={-1}>{post.title}</h1>;
const toggle = <div role="button" tabIndex={0} onClick={expand} />;

Options#

OptionDefaultWhat it does
tags[]Elements that can have a tabIndex.
roles["tabpanel"]Non-interactive roles that can have a tabIndex. Your list replaces the default.
allowExpressionValuestrueAllow a tabIndex or a role that is only known at run time, such as a variable.
example.jsxwith { "tags": ["pre"] }No problems
const code = <pre tabIndex={0}>{source}</pre>;
example.jsxNo problems
const panel = <div role="tabpanel" tabIndex={0} />;
example.jsxwith { "roles": ["region"] }1 problem
const panel = <div role="tabpanel" tabIndex={0} />;

With allowExpressionValues: false, a tabIndex that is a variable is reported on every element, even on a <button> or a component:

example.jsxNo problems
const row = <div tabIndex={rowTabIndex} />;
const next = <button tabIndex={rowTabIndex} />;
const cell = <Cell tabIndex={rowTabIndex} />;
example.jsxwith { "allowExpressionValues": false }3 problems
const row = <div tabIndex={rowTabIndex} />;
const next = <button tabIndex={rowTabIndex} />;
const cell = <Cell tabIndex={rowTabIndex} />;

A conditional doesn't count as unknown. The rule looks at its first branch:

example.jsx1 problem
const row = <div tabIndex={isActive ? 0 : -1} />;