Disallow tabindex on non-interactive elements.
Adding tabindex="0" to a <div>, <section>, etc. puts it in the keyboard tab order without supplying any keyboard semantics — users reach the element but have no way to operate it, and screen readers announce the tag with no hint of interactivity.
If the element is meant to be interactive, give it an explicit ARIA role (button, checkbox, …) and wire up the appropriate keyboard event handlers. If it isn't meant to be interactive, remove the tabindex.
tabindex="-1" is exempt — it marks an element as programmatically focusable but skipped by the Tab key, the canonical pattern for scroll-to-focus targets, focus restoration, and composite-widget children. See template-require-aria-activedescendant-tabindex.
<canvas> is always exempt. The HTML spec does not classify <canvas> as interactive content, but it is routinely used as an interactive drawing or game surface, and tabindex is required to make it keyboard-accessible. Flagging <canvas tabindex="0"> would produce unhelpful noise for these legitimate use-cases.
This rule forbids the following:
<template>
<div tabindex="0"></div>
<article tabindex="0">Story</article>
<div role="article" tabindex="0"></div>
<a tabindex="0">Not a link (missing href)</a>
</template>This rule allows the following:
<template>
{{! Interactive native elements }}
<button tabindex="0">Click</button>
<a href="/x" tabindex="0">Link</a>
<input tabindex="-1" />
{{! Non-interactive element with an interactive ARIA role }}
<div role="button" tabindex="0"></div>
<div role="checkbox" tabindex="0" aria-checked="false"></div>
{{! tabindex="-1" — focusable but not in tab order }}
<div tabindex="-1"></div>
<section tabindex="-1">scroll target</section>
{{! Dynamic role — conservatively skipped }}
<div role={{this.role}} tabindex="0"></div>
{{! role="tabpanel" — default allowlist (see Options) }}
<div role="tabpanel" tabindex="0" aria-labelledby="tab-1">Panel</div>
{{! <canvas> — exempted because canvas needs tabindex to be keyboard-accessible }}
<canvas tabindex="0"></canvas>
</template>-
roles(default["tabpanel"]) — non-interactive ARIA roles exempted from this rule. Elements carrying one of these roles may havetabindexwithout triggering a flag.The default value (
["tabpanel"]) matches jsx-a11y's recommended config. The WAI-ARIA APG Tabs pattern gives panelstabindex="0"when the panel's content isn't itself focusable, so keyboard users can page through panels. Flagging tabpanel-with-tabindex as a violation would break the canonical Tabs pattern.Use an empty array (
roles: []) to disable the default exemption — matching jsx-a11y's strict config. Use a wider list (e.g.roles: ["tabpanel", "region"]) to exempt additional roles where your project usestabindexlegitimately (scrollable regions, etc.).