jsx-a11y/no-noninteractive-element-to-interactive-role

Disallow interactive ARIA roles on non-interactive elements

Also called jsx_a11y/no-noninteractive-element-to-interactive-role

A role only changes what a screen reader announces. An <h2 role="button"> is announced as a button, but you can't reach it with the Tab key, it doesn't react to Enter or Space, and it's no longer in the list of headings. Keep the element and put a real <button> or <a> inside or around it.

Non-interactive elements include headings, <p>, <li>, <img>, <article>, <main> and <table>. <div> and <span> don't mean anything by themselves, so they can take any role. Only a role written as a string is checked.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"jsx-a11y/no-noninteractive-element-to-interactive-role" = "error" # or "warn"

Examples#

example.jsx2 problems
const title = <h2 role="button" onClick={toggle}>Details</h2>;
const avatar = <img src={user.photo} alt={user.name} role="link" />;
example.jsxNo problems
const title = (
  <h2>
    <button onClick={toggle}>Details</button>
  </h2>
);

const avatar = (
  <a href={user.url}>
    <img src={user.photo} alt={user.name} />
  </a>
);

const custom = <div role="button" tabIndex={0} onClick={toggle} />;

Options#

The option is an object that maps an element to the roles it's allowed to have. The default lets lists be menus, tabs and trees:

{
  "ul": ["menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"],
  "ol": ["menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"],
  "li": ["menuitem", "menuitemcheckbox", "menuitemradio", "row", "tab", "treeitem"],
  "fieldset": ["radiogroup", "presentation"]
}

Your object replaces the default, so repeat the entries you want to keep.

example.jsxNo problems
const tabs = (
  <ul role="tablist">
    <li role="tab">Profile</li>
  </ul>
);
example.jsxwith { "table": ["grid"] }No problems
const sheet = <table role="grid" />;
example.jsxwith { "table": ["grid"] }1 problem
const tabs = <ul role="tablist" />;