Skip to content

Commit 19039d6

Browse files
committed
feat: add template-no-aria-label-misuse — flag aria-label on roles with prohibited name-from-author
1 parent 414d6d5 commit 19039d6

5 files changed

Lines changed: 513 additions & 43 deletions

File tree

README.md

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -256,40 +256,41 @@ To disable a rule for an entire `.gjs`/`.gts` file, use a regular ESLint file-le
256256

257257
### Accessibility
258258

259-
| Name                                            | Description | 💼 | 🔧 | 💡 |
260-
| :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | :- | :- | :- |
261-
| [template-link-href-attributes](docs/rules/template-link-href-attributes.md) | require href attribute on link elements | 📋 | | |
262-
| [template-no-abstract-roles](docs/rules/template-no-abstract-roles.md) | disallow abstract ARIA roles | 📋 | | |
263-
| [template-no-accesskey-attribute](docs/rules/template-no-accesskey-attribute.md) | disallow accesskey attribute | 📋 | 🔧 | |
264-
| [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 | 📋 | | |
266-
| [template-no-autofocus-attribute](docs/rules/template-no-autofocus-attribute.md) | disallow autofocus attribute | 📋 | 🔧 | |
267-
| [template-no-duplicate-landmark-elements](docs/rules/template-no-duplicate-landmark-elements.md) | disallow duplicate landmark elements without unique labels | 📋 | | |
268-
| [template-no-empty-headings](docs/rules/template-no-empty-headings.md) | disallow empty heading elements | 📋 | | |
269-
| [template-no-heading-inside-button](docs/rules/template-no-heading-inside-button.md) | disallow heading elements inside button elements | 📋 | | |
270-
| [template-no-invalid-aria-attributes](docs/rules/template-no-invalid-aria-attributes.md) | disallow invalid aria-* attributes | 📋 | | |
271-
| [template-no-invalid-interactive](docs/rules/template-no-invalid-interactive.md) | disallow non-interactive elements with interactive handlers | 📋 | | |
272-
| [template-no-invalid-link-text](docs/rules/template-no-invalid-link-text.md) | disallow invalid or uninformative link text content | 📋 | | |
273-
| [template-no-invalid-link-title](docs/rules/template-no-invalid-link-title.md) | disallow invalid title attributes on link elements | 📋 | | |
274-
| [template-no-invalid-role](docs/rules/template-no-invalid-role.md) | disallow invalid ARIA roles | 📋 | | |
275-
| [template-no-nested-interactive](docs/rules/template-no-nested-interactive.md) | disallow nested interactive elements | 📋 | | |
276-
| [template-no-nested-landmark](docs/rules/template-no-nested-landmark.md) | disallow nested landmark elements | 📋 | | |
277-
| [template-no-pointer-down-event-binding](docs/rules/template-no-pointer-down-event-binding.md) | disallow pointer down event bindings | 📋 | | |
278-
| [template-no-positive-tabindex](docs/rules/template-no-positive-tabindex.md) | disallow positive tabindex values | 📋 | | |
279-
| [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 | 📋 | | |
287-
| [template-require-mandatory-role-attributes](docs/rules/template-require-mandatory-role-attributes.md) | require mandatory ARIA attributes for ARIA roles | 📋 | | |
288-
| [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 | 📋 | | |
259+
| Name                                            | Description | 💼 | 🔧 | 💡 |
260+
| :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :- | :- | :- |
261+
| [template-link-href-attributes](docs/rules/template-link-href-attributes.md) | require href attribute on link elements | 📋 | | |
262+
| [template-no-abstract-roles](docs/rules/template-no-abstract-roles.md) | disallow abstract ARIA roles | 📋 | | |
263+
| [template-no-accesskey-attribute](docs/rules/template-no-accesskey-attribute.md) | disallow accesskey attribute | 📋 | 🔧 | |
264+
| [template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md) | disallow aria-hidden on body element | 📋 | 🔧 | |
265+
| [template-no-aria-label-misuse](docs/rules/template-no-aria-label-misuse.md) | disallow aria-label and aria-labelledby on elements whose role prohibits an accessible name | | | |
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 | 📋 | | |
267+
| [template-no-autofocus-attribute](docs/rules/template-no-autofocus-attribute.md) | disallow autofocus attribute | 📋 | 🔧 | |
268+
| [template-no-duplicate-landmark-elements](docs/rules/template-no-duplicate-landmark-elements.md) | disallow duplicate landmark elements without unique labels | 📋 | | |
269+
| [template-no-empty-headings](docs/rules/template-no-empty-headings.md) | disallow empty heading elements | 📋 | | |
270+
| [template-no-heading-inside-button](docs/rules/template-no-heading-inside-button.md) | disallow heading elements inside button elements | 📋 | | |
271+
| [template-no-invalid-aria-attributes](docs/rules/template-no-invalid-aria-attributes.md) | disallow invalid aria-* attributes | 📋 | | |
272+
| [template-no-invalid-interactive](docs/rules/template-no-invalid-interactive.md) | disallow non-interactive elements with interactive handlers | 📋 | | |
273+
| [template-no-invalid-link-text](docs/rules/template-no-invalid-link-text.md) | disallow invalid or uninformative link text content | 📋 | | |
274+
| [template-no-invalid-link-title](docs/rules/template-no-invalid-link-title.md) | disallow invalid title attributes on link elements | 📋 | | |
275+
| [template-no-invalid-role](docs/rules/template-no-invalid-role.md) | disallow invalid ARIA roles | 📋 | | |
276+
| [template-no-nested-interactive](docs/rules/template-no-nested-interactive.md) | disallow nested interactive elements | 📋 | | |
277+
| [template-no-nested-landmark](docs/rules/template-no-nested-landmark.md) | disallow nested landmark elements | 📋 | | |
278+
| [template-no-pointer-down-event-binding](docs/rules/template-no-pointer-down-event-binding.md) | disallow pointer down event bindings | 📋 | | |
279+
| [template-no-positive-tabindex](docs/rules/template-no-positive-tabindex.md) | disallow positive tabindex values | 📋 | | |
280+
| [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 | 📋 | | |
288+
| [template-require-mandatory-role-attributes](docs/rules/template-require-mandatory-role-attributes.md) | require mandatory ARIA attributes for ARIA roles | 📋 | | |
289+
| [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 | 📋 | | |
293294

294295
### Best Practices
295296

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# ember/template-no-aria-label-misuse
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Flag `aria-label` and `aria-labelledby` on elements whose computed role
6+
prohibits an accessible name from author. On a plain `<div>` the role is
7+
`generic`, whose [`prohibitedProps` per WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/#generic)
8+
list both `aria-label` and `aria-labelledby` — assistive technology is
9+
expected to ignore them.
10+
11+
## How the role is resolved
12+
13+
Roles are looked up via [`aria-query`](https://www.npmjs.com/package/aria-query),
14+
the authoritative WAI-ARIA data package:
15+
16+
- **Explicit**: if the element has a static `role="..."`, that role is used.
17+
`role="presentation"` and `role="none"` cause the element to be skipped
18+
(author has removed it from the a11y tree).
19+
- **Implicit**: `aria-query.elementRoles` gives the spec-mapped role for
20+
each HTML tag. Conditional entries (e.g. `<section aria-label="...">`
21+
maps to `region`, `<a href>` maps to `link`) are matched against the
22+
element's static attributes; the most specific match wins.
23+
24+
If no role can be resolved (unknown tag, component invocation, or
25+
element without an aria-query entry), the rule skips — consistent with
26+
the plugin's "when in doubt, don't flag" stance.
27+
28+
## Escape hatches (not flagged by default)
29+
30+
- Elements with `tabindex` (any value). An explicit `tabindex` signals
31+
author-intent-to-interact, even when the computed ARIA role is still
32+
generic. Flagging here has a high false-positive cost (the author
33+
_wants_ the label read on focus) relative to the true-positive it
34+
would catch. Disable this hatch with `strictTabindex: true` below.
35+
- Elements with `role="presentation"` / `role="none"`.
36+
- Elements whose role is inherently nameable (e.g. `button`, `link`,
37+
`main`, `navigation`, `region`).
38+
39+
## Configuration
40+
41+
- `strictTabindex` (`boolean`, default `false`): when `true`, the
42+
tabindex escape hatch is disabled — a `<div tabindex="0" aria-label="x">`
43+
is flagged as strictly as any other generic element. Enable this for
44+
strict spec-role enforcement.
45+
46+
```js
47+
module.exports = {
48+
rules: {
49+
'ember/template-no-aria-label-misuse': ['error', { strictTabindex: true }],
50+
},
51+
};
52+
```
53+
54+
## Examples
55+
56+
Forbids:
57+
58+
```hbs
59+
<div aria-label='dialog'>...</div>
60+
<span aria-labelledby='title'>...</span>
61+
<p aria-label='note'>Note text</p>
62+
<a aria-label='missing-href'>...</a>
63+
<img aria-label='x' alt='' src='/y.png' />
64+
```
65+
66+
Allows:
67+
68+
```hbs
69+
<button aria-label='Close'>x</button>
70+
<main aria-label='Primary'>...</main>
71+
<section aria-label='About'>...</section>
72+
{{! becomes role=region }}
73+
<form aria-label='Search'>...</form>
74+
{{! becomes role=form }}
75+
<div role='button' aria-label='Custom'>...</div>
76+
<span tabindex='0' aria-label='Focusable'>...</span>
77+
```
78+
79+
## References
80+
81+
- [WAI-ARIA 1.2: Accessible Name Calculation](https://www.w3.org/TR/wai-aria-1.2/#namecalculation)
82+
- [WAI-ARIA 1.2: `aria-label` property definition](https://www.w3.org/TR/wai-aria-1.2/#aria-label)
83+
- [HTML-AAM: ARIA role mappings](https://www.w3.org/TR/html-aam-1.1/#html-element-role-mappings)
84+
- [`aria-query`](https://www.npmjs.com/package/aria-query) (authoritative ARIA data, already a dep of this plugin)
85+
- Rule inspired by [`html-validate`'s `aria-label-misuse`](https://gitlab.com/html-validate/html-validate/-/blob/v10.13.1/src/rules/aria-label-misuse.ts) (MIT).

0 commit comments

Comments
 (0)