From e17c1b903ab32228efcf2f6809a01d506c34a743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20R=C3=B8ed?= Date: Mon, 27 Apr 2026 21:28:45 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20template-no-invalid-interactive=20?= =?UTF-8?q?=E2=80=94=20honor=20role=3Dpresentation/none=20and=20aria-hidde?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/rules/template-no-invalid-interactive.md | 14 +++ lib/rules/template-no-invalid-interactive.js | 69 ++++++++++- .../rules/template-no-invalid-interactive.js | 114 ++++++++++++++++-- 3 files changed, 186 insertions(+), 11 deletions(-) diff --git a/docs/rules/template-no-invalid-interactive.md b/docs/rules/template-no-invalid-interactive.md index 0570c2408f..2a41b0c253 100644 --- a/docs/rules/template-no-invalid-interactive.md +++ b/docs/rules/template-no-invalid-interactive.md @@ -46,6 +46,20 @@ Examples of **correct** code for this rule: ``` +## Escape hatches + +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. Explicit `aria-hidden="false"` / `{{false}}` still flags. +- **`role="presentation"` / `role="none"`** — the author asserts the element is decorative. We accept `role="presentation"` and `role="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-match `hasPresentationRole`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/hasPresentationRole.js) for consistency with [WAI-ARIA 1.2 §4.1](https://www.w3.org/TR/wai-aria-1.2/#host_general_role) role-fallback semantics. + +The valueless `aria-hidden` case (e.g. `
`) is [genuinely contested](https://www.scottohara.me/blog/2018/05/05/hidden-vs-none.html) — 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. + +### Related checks outside this rule's scope + +- **`role="presentation"` on focusable elements** — per [WAI-ARIA 1.2 §4.6 Conflict Resolution](https://www.w3.org/TR/wai-aria-1.2/#conflict_resolution_presentation_none), browsers ignore `role="presentation"` on focusable elements. [axe-core's `presentation-role-conflict`](https://dequeuniversity.com/rules/axe/4.10/presentation-role-conflict) flags this pattern as an authoring error. This rule does not detect the conflict: the escape hatch check runs before `isInteractive(node)`, so a focusable element with `role="presentation"` returns early and is silently exempted. Interactive-handler cases on plain `