You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|[template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md)| disallow aria-hidden on body element | 📋 | 🔧 ||
265
-
|[template-no-aria-unsupported-elements](docs/rules/template-no-aria-unsupported-elements.md)| disallow ARIA roles, states, and properties on elements that do not support them | 📋 |||
|[template-no-redundant-role](docs/rules/template-no-redundant-role.md)| disallow redundant role attributes | 📋 | 🔧 ||
280
-
|[template-no-unsupported-role-attributes](docs/rules/template-no-unsupported-role-attributes.md)| disallow ARIA attributes that are not supported by the element role | 📋 | 🔧 ||
281
-
|[template-no-whitespace-within-word](docs/rules/template-no-whitespace-within-word.md)| disallow excess whitespace within words (e.g. "W e l c o m e") | 📋 |||
282
-
|[template-require-aria-activedescendant-tabindex](docs/rules/template-require-aria-activedescendant-tabindex.md)| require non-interactive elements with aria-activedescendant to have tabindex | 📋 | 🔧 ||
283
-
|[template-require-context-role](docs/rules/template-require-context-role.md)| require ARIA roles to be used in appropriate context | 📋 |||
284
-
|[template-require-iframe-title](docs/rules/template-require-iframe-title.md)| require iframe elements to have a title attribute | 📋 |||
285
-
|[template-require-input-label](docs/rules/template-require-input-label.md)| require label for form input elements | 📋 |||
286
-
|[template-require-lang-attribute](docs/rules/template-require-lang-attribute.md)| require lang attribute on html element | 📋 |||
|[template-require-media-caption](docs/rules/template-require-media-caption.md)| require captions for audio and video elements | 📋 |||
289
-
|[template-require-presentational-children](docs/rules/template-require-presentational-children.md)| require presentational elements to only contain presentational children | 📋 |||
290
-
|[template-require-valid-alt-text](docs/rules/template-require-valid-alt-text.md)| require valid alt text for images and other elements | 📋 |||
291
-
|[template-require-valid-form-groups](docs/rules/template-require-valid-form-groups.md)| require grouped form controls to have fieldset/legend or WAI-ARIA group labeling ||||
292
-
|[template-table-groups](docs/rules/template-table-groups.md)| require table elements to use table grouping elements | 📋 |||
|[template-click-events-have-key-events](docs/rules/template-click-events-have-key-events.md)| require a clickable non-interactive element to have at least one keyboard event listener ||||
262
+
|[template-link-href-attributes](docs/rules/template-link-href-attributes.md)| require href attribute on link elements | 📋 |||
|[template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md)| disallow aria-hidden on body element | 📋 | 🔧 ||
266
+
|[template-no-aria-unsupported-elements](docs/rules/template-no-aria-unsupported-elements.md)| disallow ARIA roles, states, and properties on elements that do not support them | 📋 |||
|[template-no-redundant-role](docs/rules/template-no-redundant-role.md)| disallow redundant role attributes | 📋 | 🔧 ||
281
+
|[template-no-unsupported-role-attributes](docs/rules/template-no-unsupported-role-attributes.md)| disallow ARIA attributes that are not supported by the element role | 📋 | 🔧 ||
282
+
|[template-no-whitespace-within-word](docs/rules/template-no-whitespace-within-word.md)| disallow excess whitespace within words (e.g. "W e l c o m e") | 📋 |||
283
+
|[template-require-aria-activedescendant-tabindex](docs/rules/template-require-aria-activedescendant-tabindex.md)| require non-interactive elements with aria-activedescendant to have tabindex | 📋 | 🔧 ||
284
+
|[template-require-context-role](docs/rules/template-require-context-role.md)| require ARIA roles to be used in appropriate context | 📋 |||
285
+
|[template-require-iframe-title](docs/rules/template-require-iframe-title.md)| require iframe elements to have a title attribute | 📋 |||
286
+
|[template-require-input-label](docs/rules/template-require-input-label.md)| require label for form input elements | 📋 |||
287
+
|[template-require-lang-attribute](docs/rules/template-require-lang-attribute.md)| require lang attribute on html element | 📋 |||
|[template-require-media-caption](docs/rules/template-require-media-caption.md)| require captions for audio and video elements | 📋 |||
290
+
|[template-require-presentational-children](docs/rules/template-require-presentational-children.md)| require presentational elements to only contain presentational children | 📋 |||
291
+
|[template-require-valid-alt-text](docs/rules/template-require-valid-alt-text.md)| require valid alt text for images and other elements | 📋 |||
292
+
|[template-require-valid-form-groups](docs/rules/template-require-valid-form-groups.md)| require grouped form controls to have fieldset/legend or WAI-ARIA group labeling ||||
293
+
|[template-table-groups](docs/rules/template-table-groups.md)| require table elements to use table grouping elements | 📋 |||
Enforce that a clickable non-interactive element has at least one keyboard event listener.
6
+
7
+
When a `{{on "click" …}}` modifier is attached to a non-interactive DOM element (e.g. `<div>`, `<span>`, `<a>` without `href`), keyboard-only users can't reach the handler through the keyboard path alone. Adding `{{on "keydown" …}}`, `{{on "keyup" …}}`, or `{{on "keypress" …}}` — along with appropriate `role`/`tabindex` to make the element focusable — restores keyboard parity.
8
+
9
+
## On "normative basis"
10
+
11
+
[WCAG 2.1 SC 2.1.1 Keyboard (Level A)](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) requires all functionality to be operable via the keyboard. The click-without-keydown shape is the canonical violation in practice. However, [Understanding 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) explicitly notes that "a custom button only reacting to Enter" still satisfies the SC — so the click+keydown pairing isn't literally spec-mandated. It's a peer-plugin convention (jsx-a11y, vuejs-accessibility, lit-a11y, @angular-eslint/template all use it) as the strongest static-analysis proxy for SC 2.1.1 violations. This rule follows the convention.
12
+
13
+
This rule is complementary to [`ember/template-no-invalid-interactive`](./template-no-invalid-interactive.md):
14
+
15
+
-`template-no-invalid-interactive` takes a stricter stance: don't use DOM event modifiers on non-interactive elements at all. Steers authors toward native interactive elements.
16
+
-`template-click-events-have-key-events` is permissive: if you _do_ use `{{on "click" …}}` on a non-interactive element, at least pair it with a keyboard listener.
17
+
18
+
Enable one, the other, or both depending on your project's stance.
19
+
20
+
## Examples
21
+
22
+
This rule **forbids** the following:
23
+
24
+
```gjs
25
+
<template>
26
+
<div {{on "click" this.onClick}}></div>
27
+
<span {{on "click" this.onClick}}>text</span>
28
+
<a {{on "click" this.onClick}}>Not a link (missing href)</a>
29
+
</template>
30
+
```
31
+
32
+
This rule **allows** the following:
33
+
34
+
```gjs
35
+
<template>
36
+
{{! Interactive elements — keyboard is built in }}
0 commit comments