jsx-a11y/interactive-supports-focus

Require elements with an interactive role and an event handler to be focusable

has suggestions

Also called jsx_a11y/interactive-supports-focus

role="button" makes a screen reader say "button", but it doesn't make a div behave like one. Without a tabIndex, the element never gets focus, so nobody can reach it with the keyboard.

The rule reports an element such as div or span that has an interactive role, a mouse or keyboard handler, and no tabIndex. In an editor, it suggests adding tabIndex={0}, which puts the element in the Tab order. For roles that a parent widget moves focus to, such as tab, option and menuitem, it also suggests tabIndex={-1}.

The rule skips components, controls such as button and input, elements with disabled, aria-disabled="true" or aria-hidden, and elements that already have their own meaning, such as li, p and h1.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"jsx-a11y/interactive-supports-focus" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"jsx-a11y/interactive-supports-focus" = ["error", {
  tabbable = [
    "button",
    "checkbox",
    "link",
    "searchbox",
    "spinbutton",
    "switch",
    "textbox",
  ],
}]

Examples#

example.jsx2 problems
const save = <div role="button" onClick={saveDraft}>Save</div>;
const tab = <span role="tab" onClick={showBilling}>Billing</span>;
example.jsxNo problems
const save = <button onClick={saveDraft}>Save</button>;
const publish = <div role="button" tabIndex={0} onClick={publishPost}>Publish</div>;
const tab = <span role="tab" tabIndex={-1} onClick={showBilling}>Billing</span>;

Options#

OptionDefaultWhat it does
tabbable["button", "checkbox", "link", "searchbox", "spinbutton", "switch", "textbox"]Roles that must be in the Tab order. Your list replaces the default.

The option only changes the message and the suggestions. A role in the list gets "must be tabbable" and only the tabIndex={0} suggestion. Any other role gets "must be focusable". The same elements are reported either way, and any tabIndex is enough.

example.jsxwith { "tabbable": [] }2 problems
const save = <div role="button" onClick={saveDraft}>Save</div>;
const tab = <span role="tab" onClick={showBilling}>Billing</span>;