Skip to content

Commit dbaff2b

Browse files
committed
docs: explain escape-hatch scope + point at complementary axe rules
Re-examined the original #33 review concern ("role=presentation escape hatch contradicts axe-core's presentation-role-conflict"). Tracing through actual cases shows the "narrowing" proposal was a no-op — isInteractive already catches focusable hosts before the escape hatch matters. The rule's behavior is correct; the docs weren't documenting the scope clearly. Added: - Explicit "Escape hatches" section naming the two escapes (aria-hidden truthy, role=presentation/none) with rationale. - Valueless-aria-hidden contested-semantics note pointing at Scott O'Hara's hidden-vs-none post for context. - "Related checks outside this rule's scope" subsection naming axe-core's presentation-role-conflict (distinct concern — role conflict on focusable) and click-events-have-key-events (distinct concern — handler without keyboard equivalent) so users know to layer those on top when appropriate. No rule-behavior changes.
1 parent d7f6223 commit dbaff2b

1 file changed

Lines changed: 14 additions & 0 deletions

File tree

docs/rules/template-no-invalid-interactive.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ Examples of **correct** code for this rule:
4646
</template>
4747
```
4848

49+
## Escape hatches
50+
51+
An element opts out of this rule's handler-on-non-interactive check in two cases:
52+
53+
- **`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.
54+
- **`role="presentation"` / `role="none"`** — the author asserts the element is decorative. Matches [jsx-a11y's `isPresentationRole`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/hasPresentationRole.js) behavior.
55+
56+
The valueless `aria-hidden` case (e.g. `<div aria-hidden>`) 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.
57+
58+
### Related checks outside this rule's scope
59+
60+
- **`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's escape hatch does NOT mask the conflict — `isInteractive(node)` catches focusable elements (anchors with href, inputs, elements with tabindex, etc.) via the interactive-content check before the escape hatch matters, so handler-on-focusable-interactive cases aren't silenced here. If you want to catch the authoring error (role=presentation on a focusable element, which has no effect), layer axe-core or a dedicated rule on top.
61+
- **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's `click-events-have-key-events`](https://dequeuniversity.com/rules/axe/4.10/click-events-have-key-events) is the complementary check. If you want strictness, layer it on top.
62+
4963
## Options
5064

5165
| Name | Type | Default | Description |

0 commit comments

Comments
 (0)