# jsx-a11y/no-noninteractive-tabindex

> Disallow `tabIndex` of 0 or more on non-interactive elements

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.

{/* Generated by src/codegen/lint-rule-docs.ts. Do not edit this section. */}

## Configure

Off by default. To turn it on:

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

Every option, set to its default:

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

{/* End of the generated section. */}

## Examples

Reported:

{/* bad {"count": 2} */}
```jsx
const summary = <article tabIndex={0}>{post.summary}</article>;
const box = <div tabIndex="0" />;
```

Not reported:

{/* good */}
```jsx
const summary = <article>{post.summary}</article>;
const heading = <h1 tabIndex={-1}>{post.title}</h1>;
const toggle = <div role="button" tabIndex={0} onClick={expand} />;
```

## Options

| Option | Default | What it does |
| --- | --- | --- |
| `tags` | `[]` | Elements that can have a `tabIndex`. |
| `roles` | `["tabpanel"]` | Non-interactive roles that can have a `tabIndex`. Your list replaces the default. |
| `allowExpressionValues` | `true` | Allow a `tabIndex` or a `role` that is only known at run time, such as a variable. |

{/* good {"options": [{"tags": ["pre"]}]} */}
```jsx
const code = <pre tabIndex={0}>{source}</pre>;
```

{/* good */}
```jsx
const panel = <div role="tabpanel" tabIndex={0} />;
```

{/* bad {"options": [{"roles": ["region"]}]} */}
```jsx
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:

{/* good */}
```jsx
const row = <div tabIndex={rowTabIndex} />;
const next = <button tabIndex={rowTabIndex} />;
const cell = <Cell tabIndex={rowTabIndex} />;
```

{/* bad {"options": [{"allowExpressionValues": false}], "count": 3} */}
```jsx
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:

{/* bad */}
```jsx
const row = <div tabIndex={isActive ? 0 : -1} />;
```
