💼 This rule is enabled in the 📋 template-lint-migration config.
Disallow non-interactive elements with interactive handlers
This rule prevents adding interactive event handlers (like onclick, onkeydown, etc.) to non-interactive HTML elements without proper ARIA roles.
Examples of incorrect code for this rule:
<template>
<div onclick={{this.handleClick}}>Click me</div>
</template><template>
<span onkeydown={{this.handleKey}}>Press key</span>
</template>Examples of correct code for this rule:
<template>
<button onclick={{this.handleClick}}>Click me</button>
</template><template>
<div role="button" onclick={{this.handleClick}}>Click me</div>
</template><template>
<button {{on "click" this.handleClick}}>Click me</button>
</template>An element opts out of this rule's handler-on-non-interactive check in two cases:
aria-hidden="true"(including valueless / empty /{{true}}/{{"true"}}forms) — the author has explicitly removed the element from the accessibility tree, so a "non-interactive element with handler" is moot; AT users won't encounter it either way. Explicitaria-hidden="false"/{{false}}still flags.role="presentation"/role="none"— the author asserts the element is decorative. We acceptrole="presentation"androle="none"(case-insensitive, first recognized token of a space-separated role list per WAI-ARIA first-recognized-token rule) — a deliberate superset of jsx-a11y's exact-matchhasPresentationRolefor consistency with WAI-ARIA 1.2 §4.1 role-fallback semantics.
The valueless aria-hidden case (e.g. <div aria-hidden>) is genuinely contested — jsx-a11y, vue-a11y, axe-core, and the WAI-ARIA spec take four different positions on whether it counts as "hidden". This rule leans toward fewer false positives: flagging a handler on an author-decorated element creates friction more often than it catches real bugs.
role="presentation"on focusable elements — per WAI-ARIA 1.2 §4.6 Conflict Resolution, browsers ignorerole="presentation"on focusable elements. axe-core'spresentation-role-conflictflags this pattern as an authoring error. This rule does not detect the conflict: the escape hatch check runs beforeisInteractive(node), so a focusable element withrole="presentation"returns early and is silently exempted. Interactive-handler cases on plain<button>/<a href>etc. are still flagged normally when they lack the presentation/none opt-out. If you want to catch the authoring error itself (role=presentation on a focusable element, which has no effect at runtime), layer axe-core or a dedicated rule on top.- Click handler on a non-focusable decorated element — e.g.
<div role="presentation" {{on "click"}}>. Our escape hatch silences this by design (jsx-a11y-compat). axe-core'sclick-events-have-key-eventsis the complementary check. If you want strictness, layer it on top.
| Name | Type | Default | Description |
|---|---|---|---|
additionalInteractiveTags |
string[] |
[] |
Extra tag names to treat as interactive. |
ignoredTags |
string[] |
[] |
Tag names to skip checking. |
ignoreTabindex |
boolean |
false |
If true, tabindex does not make an element interactive. |
ignoreUsemap |
boolean |
false |
If true, usemap does not make an element interactive. |