New
Introducing React Doctor for Enterprise

react-doctor/no-noninteractive-tabindex

Reserve tabIndex for interactive elements or interactive roles; remove it from non-interactive ones.

Validation prompt

Use this to decide whether a fired diagnostic is real or a false positive.

Fires when a native HTML element that is neither inherently interactive nor carrying an interactive/allowed role has a tabIndex resolving to a non-negative integer (tabIndex="0", tabIndex={0}, or positive) — e.g. <div tabIndex="0" /> or <article tabIndex={0} />. Negative values (tabIndex="-1"), role="tabpanel" or interactive roles (button, menu, grid, listbox…), interactive tags, and custom components are exempt; non-HTML element names are skipped entirely. False positive: by default allowExpressionValues is true, so a dynamic value like <div tabIndex={someVar} /> or role={cond ? "button" : "link"} is NOT flagged — only configs that set allowExpressionValues:false make expression containers fire.

Fix prompt

Use this once validation confirms the diagnostic is real.

If the element is genuinely non-interactive, drop the tabIndex entirely so it stays out of the tab order; assistive tech already provides traversal. If you need it focusable only programmatically (e.g. to move focus after an action), use tabIndex={-1} instead of 0. If it is actually meant to be operable, give it a real interactive role plus keyboard handlers — <div role="button" tabIndex={0} onClick={…} onKeyDown={…} /> — or use a native <button>. See https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-noninteractive-tabindex

More Accessibility rules from the rules reference:

  • react-doctor/no-outline-none: Use `:focus-visible { outline: 2px solid var(--color-muted); outline-offset: 2px }` to show focus only for keyboard users while hiding it for mouse clicks
  • react-doctor/no-tiny-text: Use at least 12px for body content, 16px is ideal. Small text is hard to read, especially on high-DPI mobile screens
  • react-doctor/prefer-html-dialog: Replace the hand-rolled modal wrapper with a native <dialog> opened via dialog.showModal()
  • react-doctor/prefer-tag-over-role: Replace role with the semantic HTML element when one exists.
  • react-doctor/require-reduced-motion: Project ships a motion library but never gates animation on the user's reduced-motion preference — add `useReducedMotion()` / `<MotionConfig reducedMotion="user">` or a `@media (prefers-reduced-motion: reduce)` query