@a11yfred/neighbor 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -90
- package/CONTRIBUTING.md +40 -40
- package/README.md +4 -472
- package/RULES-CONTENT.md +240 -81
- package/RULES-CSS.md +41 -19
- package/RULES-MARKUP.md +168 -94
- package/RULES.md +47 -28
- package/lib/content-rules.js +216 -0
- package/lib/framework-rules.js +282 -0
- package/lib/helpers-webcomponents.js +134 -0
- package/lib/rules.js +374 -3
- package/neighbor-content.mjs +1 -1
- package/neighbor-eslint-angular.mjs +29 -8
- package/neighbor-eslint-lit.mjs +85 -0
- package/neighbor-eslint-remix3.mjs +49 -0
- package/neighbor-eslint-vue.mjs +26 -6
- package/neighbor-eslint-webcomponents.mjs +41 -0
- package/neighbor-eslint.mjs +13 -6
- package/neighbor-stylelint.mjs +141 -3
- package/package.json +10 -11
package/RULES-MARKUP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# @a11yfred/neighbor
|
|
1
|
+
# @a11yfred/neighbor: Markup Rules
|
|
2
2
|
|
|
3
|
-
ESLint rules for React
|
|
3
|
+
ESLint rules for React, Remix, Vue, Angular, Lit, and plain HTML.
|
|
4
4
|
|
|
5
5
|
→ [CSS rules](RULES-CSS.md) · [Content rules](RULES-CONTENT.md) · [Back to RULES.md](RULES.md)
|
|
6
6
|
|
|
@@ -9,133 +9,207 @@ ESLint rules for React / JSX, Vue SFCs, and Angular templates.
|
|
|
9
9
|
| Source | Reference |
|
|
10
10
|
| --- | --- |
|
|
11
11
|
| Adrian Roselli | [adrianroselli.com](https://adrianroselli.com) |
|
|
12
|
+
| ARIA 1.2 spec | [w3.org/TR/wai-aria-1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
13
|
+
| Deque / axe-core | deque.com: rule concepts reimplemented independently under MPL-2.0 |
|
|
14
|
+
| Eric Eggert | [yatil.net](https://yatil.net) |
|
|
12
15
|
| Heydon Pickering | [heydonworks.com](https://heydonworks.com), [inclusive-components.design](https://inclusive-components.design) |
|
|
13
|
-
|
|
|
14
|
-
| Patrick Lauke | [splintered.co.uk](https://splintered.co.uk), [patrickhlauke.github.io/aria](https://patrickhlauke.github.io/aria) |
|
|
16
|
+
| HTML Living Standard | [html.spec.whatwg.org](https://html.spec.whatwg.org/) |
|
|
15
17
|
| Karl Groves | [karlgroves.com](https://karlgroves.com) |
|
|
16
18
|
| Marcy Sutton | [marcysutton.com](https://marcysutton.com) |
|
|
17
|
-
|
|
|
19
|
+
| Patrick Lauke | [splintered.co.uk](https://splintered.co.uk), [patrickhlauke.github.io/aria](https://patrickhlauke.github.io/aria) |
|
|
20
|
+
| Scott O'Hara | [scottohara.me](https://scottohara.me) |
|
|
18
21
|
| WAI-ARIA APG | [w3.org/WAI/ARIA/apg](https://www.w3.org/WAI/ARIA/apg/) |
|
|
19
|
-
| ARIA 1.2 spec | [w3.org/TR/wai-aria-1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
20
|
-
| WebAIM Million | [webaim.org/projects/million](https://webaim.org/projects/million/) |
|
|
21
|
-
| Deque / axe-core | deque.com - rule concepts reimplemented independently under MPL-2.0 |
|
|
22
22
|
| WCAG 2.1 | [w3.org/TR/WCAG21](https://www.w3.org/TR/WCAG21/) |
|
|
23
23
|
| WCAG 2.2 | [w3.org/TR/WCAG22](https://www.w3.org/TR/WCAG22/) |
|
|
24
|
-
|
|
|
24
|
+
| WebAIM Million | [webaim.org/projects/million](https://webaim.org/projects/million/) |
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
## Core rules
|
|
28
|
+
## Core rules: all frameworks
|
|
29
29
|
|
|
30
|
-
All rules run on React, Vue, and
|
|
30
|
+
All rules run on React, Remix, Vue, Angular, and Web Components unless noted.
|
|
31
31
|
|
|
32
|
-
### Errors
|
|
32
|
+
### Errors (you must fix these)
|
|
33
33
|
|
|
34
|
-
| Rule | What it
|
|
34
|
+
| Rule | What it finds | Source |
|
|
35
35
|
| --- | --- | --- |
|
|
36
|
-
| `
|
|
37
|
-
| `no-
|
|
38
|
-
| `no-
|
|
39
|
-
| `no-
|
|
40
|
-
| `no-
|
|
41
|
-
| `no-
|
|
42
|
-
| `no-
|
|
43
|
-
| `no-
|
|
44
|
-
| `no-
|
|
45
|
-
| `no-
|
|
46
|
-
| `no-
|
|
47
|
-
| `no-
|
|
48
|
-
| `no-
|
|
49
|
-
| `no-
|
|
50
|
-
| `no-
|
|
51
|
-
| `no-
|
|
52
|
-
| `no-
|
|
53
|
-
| `no-
|
|
54
|
-
| `no-
|
|
55
|
-
| `no-
|
|
56
|
-
| `no-
|
|
57
|
-
| `no-
|
|
58
|
-
| `no-
|
|
59
|
-
| `no-
|
|
60
|
-
| `no-
|
|
61
|
-
| `no-
|
|
62
|
-
| `no-
|
|
63
|
-
| `no-
|
|
64
|
-
| `no-
|
|
65
|
-
| `no-
|
|
66
|
-
| `no-
|
|
67
|
-
| `no-
|
|
68
|
-
| `
|
|
69
|
-
| `no-
|
|
70
|
-
| `
|
|
71
|
-
| `no-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
36
|
+
| `form-field-multiple-labels` | More than one `<label>` pointing to the same `<input>`. | [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
37
|
+
| `no-aria-activedescendant-without-id` | `aria-activedescendant` without a valid ID. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
38
|
+
| `no-aria-hidden-in-link` | A link (`<a>`) that only contains hidden elements. It has no name. | Roselli - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
39
|
+
| `no-aria-hidden-on-main` | Using `aria-hidden="true"` on `<body>` or `<main>`. This hides your whole app. | [APG](https://www.w3.org/WAI/ARIA/apg/) |
|
|
40
|
+
| `no-aria-label-on-generic` | `aria-label` or `aria-labelledby` on `<div>`, `<span>`, or `<p>` without a `role`. Screen readers ignore this. | Roselli / O'Hara |
|
|
41
|
+
| `no-aria-owns-on-void` | Using `aria-owns` on elements that cannot have children (like `<img>` or `<input>`). | O'Hara / [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
42
|
+
| `no-aria-required-on-non-form` | Using `aria-required` on something that is not a form input. | [ARIA 1.2 §6.6.9](https://www.w3.org/TR/wai-aria-1.2/#aria-required) - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
43
|
+
| `no-assertive-live-overuse` | `aria-live="assertive"` without `role="alert"`. This interrupts the user when they do not expect it. | [APG](https://www.w3.org/WAI/ARIA/apg/) / Sutton / Eggert |
|
|
44
|
+
| `no-autoplay-without-controls` | Autoplaying video or audio without giving the user controls to stop it. | [SC 1.4.2](https://www.w3.org/WAI/WCAG21/Understanding/audio-control) |
|
|
45
|
+
| `no-combobox-without-expanded` | `role="combobox"` without `aria-expanded`. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) / [APG: Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) |
|
|
46
|
+
| `no-disabled-and-aria-disabled` | Using both `disabled` and `aria-disabled` at the same time. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
47
|
+
| `no-duplicate-id` | Using the same `id` twice when ARIA is trying to point to it. | [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) / [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
48
|
+
| `no-dynamic-content-without-live` | Adding HTML dynamically without using a live region to tell the screen reader. | [SC 4.1.3](https://www.w3.org/WAI/WCAG21/Understanding/status-messages) |
|
|
49
|
+
| `no-empty-button` | A `<button>` that only has hidden children and no name. | [WebAIM Million](https://webaim.org/projects/million/) - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
50
|
+
| `no-empty-table-header` | A table header (`<th>`) with no text. | [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
51
|
+
| `no-feed-without-article` | `role="feed"` without any `role="article"` inside it. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) / [APG: Feed](https://www.w3.org/WAI/ARIA/apg/patterns/feed/) |
|
|
52
|
+
| `no-group-without-name` | `role="group"` with form inputs, but no accessible name. | [APG](https://www.w3.org/WAI/ARIA/apg/) / Groves - [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
53
|
+
| `no-heading-inside-interactive` | Putting a heading inside a button or link. | Roselli / Pickering - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
54
|
+
| `no-image-role-without-name` | `role="img"` without an accessible name. | [APG](https://www.w3.org/WAI/ARIA/apg/) / O'Hara - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
55
|
+
| `no-input-type-invalid` | Using an `<input>` type that does not exist. | [HTML spec §4.10.18](https://html.spec.whatwg.org/multipage/input.html#the-input-element) - [SC 1.3.5](https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose) |
|
|
56
|
+
| `no-labelledby-missing-target` | ARIA pointing to an `id` that does not exist. | [ARIA 1.2 §6.2.4](https://www.w3.org/TR/wai-aria-1.2/#mapping_additional_nd_name) - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
57
|
+
| `no-listbox-without-option` | `role="listbox"` without any `role="option"` inside it. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) / [APG: Listbox](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/) |
|
|
58
|
+
| `no-log-with-interactive-children` | Buttons or links inside `role="log"`. | [APG: Log Role](https://www.w3.org/WAI/ARIA/apg/patterns/) |
|
|
59
|
+
| `no-meter-without-valuenow` | `role="meter"` missing `aria-valuenow`. | [APG: Meter](https://www.w3.org/WAI/ARIA/apg/patterns/meter/) |
|
|
60
|
+
| `no-mouse-only-events` | Using mouse events (like `onMouseEnter`) without adding keyboard events (like `onFocus`). | [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
61
|
+
| `no-placeholder-only` | Using only a `placeholder` to label an `<input>`. | [WebAIM Million](https://webaim.org/projects/million/) - [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
62
|
+
| `no-positive-tabindex` | Using a `tabIndex` greater than 0. This breaks the normal keyboard tab order. | WebAIM / Lauke - [SC 2.4.3](https://www.w3.org/WAI/WCAG21/Understanding/focus-order) |
|
|
63
|
+
| `no-presentation-on-focusable` | Using `role="presentation"` on something you can focus on. | Roselli / Lauke / O'Hara - [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
64
|
+
| `no-roles-without-name` | Using `role="dialog"` or similar roles without giving them an accessible name. | [APG](https://www.w3.org/WAI/ARIA/apg/) / [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
65
|
+
| `no-slider-without-range` | `role="slider"` missing range values (`aria-valuenow`, etc.). | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) / [APG: Slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) |
|
|
66
|
+
| `no-spinbutton-without-range` | `role="spinbutton"` missing range values (`aria-valuenow`, etc.). | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) / [APG: Spinbutton](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) |
|
|
67
|
+
| `no-summary-without-details` | Using `<summary>` outside of a `<details>` element. | [HTML spec](https://html.spec.whatwg.org/multipage/interactive-elements.html#the-summary-element) - [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
68
|
+
| `no-tabs-without-structure` | Missing pieces in a tab menu (like a tab without `aria-selected`). | [APG: Tabs Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
69
|
+
| `no-title-as-label` | Using only the `title` attribute to name an `<input>`. Keyboard users cannot see this. | Groves / O'Hara - [SC 2.4.6](https://www.w3.org/WAI/WCAG21/Understanding/headings-and-labels) |
|
|
70
|
+
| `no-toggle-without-checked` | A switch, checkbox, or radio button missing `aria-checked`. | [APG: Checkbox](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/) |
|
|
71
|
+
| `no-tree-without-treeitem` | `role="tree"` without any `role="treeitem"` inside it. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) / [APG: Tree View](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/) |
|
|
72
|
+
| `no-unblocked-aria-disabled` | `aria-disabled="true"` on a button or link that still has an `onClick`. The button still works even though it says it is disabled. | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
73
|
+
|
|
74
|
+
### Warnings (these are usually bad)
|
|
75
|
+
|
|
76
|
+
| Rule | What it finds | Source |
|
|
76
77
|
| --- | --- | --- |
|
|
77
|
-
| `no-
|
|
78
|
-
| `no-
|
|
79
|
-
| `no-
|
|
78
|
+
| `no-button-type-missing` | A `<button>` inside a `<form>` missing `type="button"` or `type="submit"`. | [HTML spec §4.10.18](https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element) |
|
|
79
|
+
| `no-expanded-without-controls` | `aria-expanded` without `aria-controls`. | [APG: Disclosure](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) |
|
|
80
|
+
| `no-menu-role-on-nav` | Using menu roles (like `role="menu"`). This changes how keyboards work and is usually wrong. | Roselli / Lauke / Groves - [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
81
|
+
| `no-multiple-main` | Having more than one `<main>` element. | [Axe: landmark-one-main](https://dequeuniversity.com/rules/axe/4.8/landmark-one-main) |
|
|
82
|
+
| `no-redundant-aria-hidden-with-presentation` | Using both `aria-hidden="true"` and `role="presentation"`. You only need one. | O'Hara |
|
|
83
|
+
| `no-skipped-heading-levels` | Skipping heading levels (like going from `<h1>` straight to `<h3>`). | [Axe: heading-order](https://dequeuniversity.com/rules/axe/4.8/heading-order) - [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
84
|
+
| `no-tooltip-role-misuse` | `role="tooltip"` without an `id`, or putting it on a button/link. | [APG: Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/) - [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
> **Note on components:** `no-skipped-heading-levels` and `no-multiple-main` are only checked in a single file. They cannot promise that your headings are perfect across your entire app. To test the whole app, use a tool like `@axe-core/react`.
|
|
82
87
|
|
|
83
|
-
|
|
88
|
+
### Off by default (you can turn these on)
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
These rules find real problems, but they complain a lot in most projects. You can turn them on if you want.
|
|
91
|
+
|
|
92
|
+
| Rule | What it finds | Source |
|
|
86
93
|
| --- | --- | --- |
|
|
87
|
-
| `no-application-role` | `role="application"`
|
|
88
|
-
| `no-
|
|
89
|
-
| `no-aria-roledescription` | `aria-roledescription`
|
|
90
|
-
| `no-
|
|
91
|
-
| `no-
|
|
92
|
-
| `no-href-hash` | `<a href="#">`
|
|
93
|
-
| `
|
|
94
|
-
| `no-target-blank-without-label` | `target="_blank"` without
|
|
95
|
-
| `
|
|
94
|
+
| `no-application-role` | `role="application"` (disables normal screen reader reading). | Roselli / Sutton / Lauke / [APG](https://www.w3.org/WAI/ARIA/apg/) |
|
|
95
|
+
| `no-aria-readonly` | `aria-readonly` (screen readers do not support this well). | Roselli |
|
|
96
|
+
| `no-aria-roledescription` | `aria-roledescription` (does not translate to other languages). | Roselli: Avoid aria-roledescription |
|
|
97
|
+
| `no-dialog-without-close` | A dialog without a close button. | [APG: Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) - [SC 2.1.2](https://www.w3.org/WAI/WCAG21/Understanding/no-keyboard-trap) |
|
|
98
|
+
| `no-grid-role` | `role="grid"` (almost always wrong unless building a spreadsheet). | Roselli: ARIA Grid As an Anti-Pattern |
|
|
99
|
+
| `no-href-hash` | Using `<a href="#">` instead of a `<button>`. | Sutton: Links vs Buttons - [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
100
|
+
| `no-tab-without-controls` | `role="tab"` missing `aria-controls`. | [APG: Tabs Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) |
|
|
101
|
+
| `no-target-blank-without-label` | Using `target="_blank"` without telling the user it will open a new tab. | WebAIM - [SC 3.2.2](https://www.w3.org/WAI/WCAG21/Understanding/on-input) |
|
|
102
|
+
| `prefer-aria-disabled` | Using HTML `disabled` (which hides it from the keyboard). You should use `aria-disabled` instead. | Roselli: Don't Disable Form Controls - [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
103
|
+
| `warn-role-alert` | Using `role="alert"`. You should use `role="status"` for things that are not urgent. | [APG](https://www.w3.org/WAI/ARIA/apg/) / Roselli / Sutton - [SC 4.1.3](https://www.w3.org/WAI/WCAG21/Understanding/status-messages) |
|
|
96
104
|
|
|
97
105
|
---
|
|
98
106
|
|
|
99
|
-
## Portability rules
|
|
107
|
+
## Portability rules: Vue and Angular only
|
|
100
108
|
|
|
101
109
|
These rules cover gaps in `eslint-plugin-jsx-a11y` that have no equivalent in `eslint-plugin-vuejs-accessibility` or `@angular-eslint/eslint-plugin-template`. React projects get these from jsx-a11y already.
|
|
102
110
|
|
|
103
111
|
| Rule | What it flags | Source |
|
|
104
112
|
| --- | --- | --- |
|
|
113
|
+
| `no-access-key` | `accessKey` attribute - conflicts with AT and browser shortcuts | [SC 2.1.4](https://www.w3.org/WAI/WCAG21/Understanding/character-key-shortcuts) |
|
|
105
114
|
| `no-anchor-ambiguous-text` | Ambiguous link text ("click here", "read more", "learn more") | [SC 2.4.4](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context) |
|
|
106
115
|
| `no-anchor-no-content` | `<a>` with no text content and no accessible name | [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
107
116
|
| `no-aria-activedescendant-no-tabindex` | `aria-activedescendant` on an element without `tabindex` | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
108
|
-
| `no-invalid-aria-prop-value` | Invalid values on ARIA state/property attributes | [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
109
117
|
| `no-autocomplete-invalid` | Invalid `autocomplete` token values | [SC 1.3.5](https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose) |
|
|
110
|
-
| `no-heading-no-content` | Headings (`<h1
|
|
118
|
+
| `no-heading-no-content` | Headings (`<h1>`-`<h6>`) with no text content | [SC 2.4.6](https://www.w3.org/WAI/WCAG21/Understanding/headings-and-labels) |
|
|
111
119
|
| `no-iframe-no-title` | `<iframe>` without a `title` attribute | [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
112
120
|
| `no-img-redundant-alt` | Alt text containing "image", "photo", or "picture" | [SC 1.1.1](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content) |
|
|
113
|
-
| `no-
|
|
114
|
-
| `no-noninteractive-to-interactive-role` | Non-interactive elements given interactive ARIA roles without keyboard handlers | [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) / [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
121
|
+
| `no-invalid-aria-prop-value` | Invalid values on ARIA state/property attributes | [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
115
122
|
| `no-noninteractive-tabindex` | `tabindex` on a non-interactive element with no interactive role | [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
123
|
+
| `no-noninteractive-to-interactive-role` | Non-interactive elements given interactive ARIA roles without keyboard handlers | [SC 2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) / [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
124
|
+
| `no-role-supports-aria-props` | ARIA properties applied to roles that do not support them | [ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) |
|
|
125
|
+
| `no-scope-on-td` | `scope` attribute on `<td>` - only valid on `<th>` | [SC 1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
116
126
|
| `prefer-semantic-element` | `<div role="button">` where a native element would be correct | [SC 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
|
|
128
|
+
## Framework-specific rules: React, Vue, Angular, Remix, Lit
|
|
129
|
+
|
|
130
|
+
These rules apply only to specific frameworks using their respective parser plugins.
|
|
131
|
+
|
|
132
|
+
| Rule | Severity | What it finds |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `angular-host-a11y` | error | Setting `role: 'button'` on an Angular component without setting a `tabindex`. |
|
|
135
|
+
| `lit-no-autofocus` | error | Using `autofocus` inside a Lit `html` template. |
|
|
136
|
+
| `remix-route-title-missing` | error | Remix route missing a `title` in its `meta` export. |
|
|
137
|
+
| `vue-click-key-events` | error | Adding `@click` to something that is not a button, but forgetting to add keyboard events. |
|
|
138
|
+
| `react-fragment-ruins-aria` | warn | React `<Fragment>` (or `<>`) with ARIA attributes. The attributes get deleted when the HTML is created. |
|
|
139
|
+
| `react-spa-focus-management` | warn | Using `useNavigate` or `<Link>` without managing focus. Keyboard users get lost when the page changes. |
|
|
140
|
+
| `vue-transition-live-region` | warn | `<Transition>` around a live region. Screen readers will not announce this correctly. |
|
|
141
|
+
| `angular-router-focus-management` | off | `<router-outlet>` without managing focus when the page changes. |
|
|
142
|
+
| `vue-router-focus-management` | off | `<RouterView>` without managing focus or using `aria-live`. |
|
|
143
|
+
|
|
144
|
+
## Framework-Specific Omissions
|
|
145
|
+
|
|
146
|
+
Neighbor is designed to run alongside standard accessibility linters (like `eslint-plugin-jsx-a11y`, `eslint-plugin-vuejs-accessibility`, `@angular-eslint/eslint-plugin-template`, and `eslint-plugin-lit-a11y`).
|
|
147
|
+
|
|
148
|
+
If you have these standard linters installed, Neighbor will automatically **turn off** its own redundant base rules to prevent duplicate warnings. If you choose *not* to install the standard linters, Neighbor will keep these base rules enabled to protect your codebase.
|
|
149
|
+
|
|
150
|
+
### Vue (`eslint-plugin-vuejs-accessibility`)
|
|
151
|
+
|
|
152
|
+
Omitted base rules: `no-heading-no-content`, `no-iframe-no-title`, `no-access-key`, `no-img-redundant-alt`, `no-anchor-no-content`, `no-invalid-aria-prop-value`, `no-role-supports-aria-props`.
|
|
153
|
+
|
|
154
|
+
### Angular (`@angular-eslint/eslint-plugin-template`)
|
|
155
|
+
|
|
156
|
+
Omitted base rules: `no-heading-no-content`, `no-anchor-no-content`, `no-img-redundant-alt`, `no-scope-on-td`, `no-invalid-aria-prop-value`.
|
|
157
|
+
|
|
158
|
+
### Lit (`eslint-plugin-lit-a11y`)
|
|
159
|
+
|
|
160
|
+
Omitted base rules: `no-heading-no-content`, `no-iframe-no-title`, `no-img-redundant-alt`, `no-access-key`, `no-aria-activedescendant-no-tabindex`, `no-anchor-no-content`, `no-invalid-aria-prop-value`, `no-role-supports-aria-props`, `no-scope-on-td`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Vue / Angular / Lit specific rules
|
|
165
|
+
|
|
166
|
+
These rules are added specifically to handle framework-specific ASTs or behaviors.
|
|
167
|
+
|
|
168
|
+
| Rule | What it finds | Framework |
|
|
169
|
+
| --- | --- | --- |
|
|
170
|
+
| `angular-host-a11y` | Angular component missing `tabindex` for interactive host role | Angular |
|
|
171
|
+
| `angular-router-focus-management` | SPA route without focus management | Angular |
|
|
172
|
+
| `lit-no-autofocus` | `autofocus` attribute used inside a Lit template | Lit |
|
|
173
|
+
| `no-access-key` | `accessKey` attribute | Vue, Angular, Lit |
|
|
174
|
+
| `no-anchor-ambiguous-text` | Ambiguous link text ("click here") | Vue, Angular |
|
|
175
|
+
| `no-anchor-no-content` | `<a>` with no content | Vue, Angular |
|
|
176
|
+
| `no-aria-activedescendant-no-tabindex` | `aria-activedescendant` without `tabindex` | Vue, Angular |
|
|
177
|
+
| `no-autocomplete-invalid` | Invalid `autocomplete` token | Vue, Angular |
|
|
178
|
+
| `no-heading-no-content` | Heading with no content | Vue, Angular |
|
|
179
|
+
| `no-iframe-no-title` | `<iframe>` with no `title` | Vue, Angular |
|
|
180
|
+
| `no-img-redundant-alt` | `<img>` alt text contains "image of" | Vue, Angular |
|
|
181
|
+
| `no-invalid-aria-prop-value` | Invalid ARIA attribute values | Vue, Angular |
|
|
182
|
+
| `no-noninteractive-tabindex` | Non-interactive element with `tabindex` | Vue, Angular |
|
|
183
|
+
| `no-noninteractive-to-interactive-role` | Non-interactive element with interactive role | Vue, Angular |
|
|
184
|
+
| `no-role-supports-aria-props` | Using an ARIA attribute not supported by the role | Vue, Angular |
|
|
185
|
+
| `no-scope-on-td` | `scope` on `<td>` | Vue, Angular, Lit |
|
|
186
|
+
| `prefer-semantic-element` | Use native HTML tags instead of roles | Vue, Angular |
|
|
187
|
+
| `vue-click-key-events` | `v-on:click` without key equivalent | Vue |
|
|
188
|
+
| `vue-router-focus-management` | SPA route without focus management | Vue |
|
|
189
|
+
| `vue-transition-live-region` | `<Transition>` changing live regions | Vue |
|
|
119
190
|
|
|
120
191
|
---
|
|
121
192
|
|
|
122
|
-
## Framework-specific rules
|
|
193
|
+
## Framework-specific rules: @ulam only
|
|
123
194
|
|
|
124
195
|
These rules are specific to the @ulam framework and activate only when @ulam-related imports are detected.
|
|
125
196
|
|
|
126
|
-
| Rule | Severity | What it
|
|
197
|
+
| Rule | Severity | What it finds |
|
|
127
198
|
| --- | --- | --- |
|
|
128
|
-
| `no-announce-in-render` | error | `announce()`
|
|
129
|
-
| `no-hash-router-in-remix` | warn | @ulam hash router
|
|
130
|
-
| `no-use-page-title-in-remix` | warn | `usePageTitle()`
|
|
199
|
+
| `no-announce-in-render` | error | Calling `announce()` during a render. This will spam screen readers. Only call it inside `useEffect`, `onMounted`, or event handlers. |
|
|
200
|
+
| `no-hash-router-in-remix` | warn | Using the @ulam hash router with `react-router`. This means your Remix migration is not finished. |
|
|
201
|
+
| `no-use-page-title-in-remix` | warn | Using `usePageTitle()` with `react-router`. This breaks Remix's `meta` export. |
|
|
131
202
|
|
|
132
|
-
The `no-announce-in-render` rule runs in
|
|
203
|
+
The `no-announce-in-render` rule runs in React, Vue, and Angular plugins with safe contexts per framework:
|
|
133
204
|
|
|
134
205
|
- **React:** `useEffect`, `useLayoutEffect`, `useCallback`, `useMemo`, and event handlers
|
|
135
206
|
- **Vue:** `onMounted`, `onUpdated`, `watch`, `watchEffect`, `nextTick`, and their variants
|
|
136
207
|
- **Angular:** `ngOnInit`, `ngAfterViewInit`, `ngAfterContentInit`, `ngOnChanges`, `ngDoCheck`, and class method event handlers
|
|
137
208
|
|
|
138
|
-
**Known
|
|
209
|
+
**Known problems with parsers:**
|
|
210
|
+
|
|
211
|
+
- **Angular templates:** The parser does not let us look up the tree. Some rules that need to look at parent elements will not work in Angular.
|
|
212
|
+
- **Web Components:** The `@html-eslint/parser` has the same problem. Rules cannot look at parent elements.
|
|
139
213
|
|
|
140
214
|
---
|
|
141
215
|
|
|
@@ -143,15 +217,15 @@ The `no-announce-in-render` rule runs in all three plugins with safe contexts pe
|
|
|
143
217
|
|
|
144
218
|
| Rule | Reason rejected |
|
|
145
219
|
| --- | --- |
|
|
146
|
-
| `
|
|
147
|
-
|
|
|
148
|
-
| `no-aria-
|
|
149
|
-
| `no-
|
|
150
|
-
| `no-
|
|
151
|
-
| `no-
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `no-
|
|
155
|
-
| `no-
|
|
156
|
-
| `no-
|
|
157
|
-
|
|
|
220
|
+
| `aria-required-on-required-form-control` | Screen readers already know what HTML `required` means. Adding `aria-required` is not needed. |
|
|
221
|
+
| DevTools console output for accessibility | This belongs in a browser extension, not a code linter. |
|
|
222
|
+
| `no-aria-controls` | Screen reader support is better now. It is required for tabs. |
|
|
223
|
+
| `no-aria-label-on-link` | Using `aria-label` on `<a>` is the right way to fix bad link text. A linter cannot check if you are doing it correctly. |
|
|
224
|
+
| `no-aria-live-on-carousel` | Just because a class says `carousel` does not mean it moves by itself. This gives too many false errors. |
|
|
225
|
+
| `no-aria-owns-circular` | This requires checking across different files, which is too hard for a simple linter. |
|
|
226
|
+
| `no-dialog-without-modal` | Non-modal dialogs are allowed. This would give too many false errors. |
|
|
227
|
+
| `no-empty-heading` | Another plugin (`jsx-a11y`) already checks this. |
|
|
228
|
+
| `no-figure-role-without-label` | Putting `role="figure"` on a `<figure>` is redundant. It flags the wrong problem. |
|
|
229
|
+
| `no-generated-content-text` | We cannot tell if CSS generated content is decorative or important. |
|
|
230
|
+
| `no-scrollable-without-focusable` | We cannot read CSS to see if an element scrolls. |
|
|
231
|
+
| `require-menu-owned-menuitem` / `require-listbox-owned-option` | In React/Vue, children are often hidden while loading. This would give too many false errors. |
|
package/RULES.md
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
# @a11yfred/neighbor
|
|
1
|
+
# @a11yfred/neighbor: Rule Index
|
|
2
2
|
|
|
3
|
-
Neighbor
|
|
3
|
+
Neighbor has rules for four different areas. Each area has its own page.
|
|
4
4
|
|
|
5
|
-
|
|
|
5
|
+
| Area | Setup | Rules page |
|
|
6
6
|
| --- | --- | --- |
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
7
|
+
| CSS | `@a11yfred/neighbor`, `@a11yfred/neighbor/stylelint` | [RULES-CSS.md](RULES-CSS.md) |
|
|
8
|
+
| HTML / Markup | `@a11yfred/neighbor/eslint`, `/eslint-vue`, `/eslint-angular`, `/webcomponents` | [RULES-MARKUP.md](RULES-MARKUP.md) |
|
|
9
|
+
| Native Mobile | `apps/ios-app`, `apps/android-app` | [iOS Rules](apps/ios-app/README.md) / [Android Rules](apps/android-app/README.md) |
|
|
10
|
+
| Text / Content | `@a11yfred/neighbor/content` | [RULES-CONTENT.md](RULES-CONTENT.md) |
|
|
10
11
|
|
|
11
12
|
---
|
|
12
13
|
|
|
13
|
-
## Markup rules
|
|
14
|
+
## Markup rules: summary
|
|
14
15
|
|
|
15
|
-
ESLint rules that
|
|
16
|
+
ESLint rules that find bad ARIA code, missing names, keyboard traps, and HTML mistakes in React, Vue, Angular, Lit, and plain HTML. Full list → [RULES-MARKUP.md](RULES-MARKUP.md)
|
|
16
17
|
|
|
17
|
-
**Errors (
|
|
18
|
+
**Errors (you must fix these):** `no-aria-label-on-generic`, `no-assertive-live-overuse`, `no-unblocked-aria-disabled`, `no-roles-without-name`, `no-group-without-name`, `no-presentation-on-focusable`, `no-log-with-interactive-children`, `no-aria-hidden-in-link`, `no-redundant-aria-hidden-with-presentation`, `no-aria-owns-on-void`, `no-title-as-label`, `no-tabs-without-structure`, `no-positive-tabindex`, `no-autoplay-without-controls`, `no-heading-inside-interactive`, `no-placeholder-only`, `no-empty-button`, `no-image-role-without-name`, `no-spinbutton-without-range`, `no-slider-without-range`, `no-combobox-without-expanded`, `no-mouse-only-events`, `no-listbox-without-option`, `no-tree-without-treeitem`, `no-feed-without-article`, `no-aria-activedescendant-without-id`, `no-duplicate-id`, `no-summary-without-details`, `no-aria-required-on-non-form`, `no-input-type-invalid`, `no-labelledby-missing-target`, `no-dynamic-content-without-live`, `form-field-multiple-labels`, `no-empty-table-header`, `no-disabled-and-aria-disabled`, `prefer-aria-disabled`
|
|
18
19
|
|
|
19
|
-
**Warnings (
|
|
20
|
+
**Warnings (these are usually bad):** `no-tooltip-role-misuse`, `no-menu-role-on-nav`, `no-button-type-missing`
|
|
20
21
|
|
|
21
|
-
**Off by default (
|
|
22
|
+
**Off by default (you can turn these on):** `no-application-role`, `no-grid-role`, `no-aria-roledescription`, `no-aria-readonly`, `no-tab-without-controls`, `no-href-hash`, `warn-role-alert`, `no-target-blank-without-label`, `no-dialog-without-close`
|
|
22
23
|
|
|
23
24
|
**Vue / Angular only:** `no-anchor-ambiguous-text`, `no-anchor-no-content`, `no-aria-activedescendant-no-tabindex`, `no-invalid-aria-prop-value`, `no-autocomplete-invalid`, `no-heading-no-content`, `no-iframe-no-title`, `no-img-redundant-alt`, `no-access-key`, `no-noninteractive-to-interactive-role`, `no-noninteractive-tabindex`, `prefer-semantic-element`, `no-role-supports-aria-props`, `no-scope-on-td`
|
|
24
25
|
|
|
@@ -26,30 +27,48 @@ ESLint rules that flag bad ARIA patterns, missing accessible names, keyboard tra
|
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
|
-
## CSS rules
|
|
30
|
+
## CSS rules: summary
|
|
30
31
|
|
|
31
|
-
Stylelint rules that
|
|
32
|
+
Stylelint rules that find bad CSS. They check if you hide focus rings, block High Contrast Mode, or ignore user preferences for motion. Full list → [RULES-CSS.md](RULES-CSS.md)
|
|
32
33
|
|
|
33
|
-
| Rule | What it
|
|
34
|
+
| Rule | What it finds |
|
|
34
35
|
| --- | --- |
|
|
36
|
+
| `neighbor/no-absolute-viewport-text` | Pure viewport units (`vw`, `vh`) for text sizing - this stops browser zoom from working |
|
|
37
|
+
| `neighbor/no-forced-colors-none` | `forced-color-adjust: none` inside `@media (forced-colors)` - this blocks Windows High Contrast Mode |
|
|
38
|
+
| `neighbor/no-outline-none` | `outline: none` outside `:focus` - this removes keyboard focus rings |
|
|
39
|
+
| `neighbor/no-text-justify` | `text-align: justify` - this creates uneven word spacing that is hard for dyslexic users to read |
|
|
40
|
+
| `neighbor/no-user-select-all-none` | `user-select: none` on text - this stops users from highlighting, copying, and translating text |
|
|
35
41
|
| `neighbor/user-preferences` | Animation, motion, and transparency without `@media (prefers-*)` fallbacks |
|
|
36
|
-
| `neighbor/no-outline-none` | `outline: none` outside `:focus` selectors - removes keyboard focus ring |
|
|
37
|
-
| `neighbor/no-forced-colors-none` | `forced-color-adjust: none` inside `@media (forced-colors)` - opts out of Windows High Contrast Mode |
|
|
38
42
|
|
|
39
43
|
---
|
|
40
44
|
|
|
41
|
-
## Content rules
|
|
45
|
+
## Content rules: summary
|
|
42
46
|
|
|
43
|
-
ESLint rules that
|
|
47
|
+
ESLint rules that find problems in your text. They check for ableist language, hard-to-understand English idioms, confusing links, and unexplained short words. All of these rules are set to `warn`. Full list → [RULES-CONTENT.md](RULES-CONTENT.md)
|
|
44
48
|
|
|
45
|
-
| Rule | What it
|
|
49
|
+
| Rule | What it finds | WCAG SC |
|
|
46
50
|
| --- | --- | --- |
|
|
47
|
-
| `no-ableist-language` |
|
|
48
|
-
| `no-
|
|
49
|
-
| `no-
|
|
50
|
-
| `no-
|
|
51
|
-
| `no-
|
|
52
|
-
| `no-
|
|
53
|
-
| `no-
|
|
54
|
-
| `no-
|
|
55
|
-
| `no-
|
|
51
|
+
| `no-ableist-language` | Offensive words about disability or framing disability as suffering ("wheelchair-bound", "suffers from", "special needs") | 3.1.1 |
|
|
52
|
+
| `no-all-caps-prose` | ALL CAPS words (screen readers might read them one letter at a time) | - |
|
|
53
|
+
| `no-ampersand-in-prose` | Using `&` instead of "and" (screen readers read this differently) | - |
|
|
54
|
+
| `no-anti-lgbtq-language` | Old or offensive words about sexual orientation and gender | - |
|
|
55
|
+
| `no-colonial-and-violent-language` | Words based on colonialism or violence (stakeholder, target population, tackle) | - |
|
|
56
|
+
| `no-deficit-language` | Words that reduce people to their bad situations (the homeless, inmate, addict) | - |
|
|
57
|
+
| `no-device-specific-action` | Words that only make sense on a desktop computer ("click here", "press enter") | - |
|
|
58
|
+
| `no-directional-language` | Instructions based on where things are on the screen ("see above", "in the right sidebar") | 1.3.3 |
|
|
59
|
+
| `no-disability-metaphor` | Using disability as a metaphor ("blind spot", "tone deaf", "paralyzed by") | - |
|
|
60
|
+
| `no-english-idiom` | Phrases or sports metaphors that are hard for non-native English speakers to understand ("slam dunk", "boil the ocean", "circle back") | 3.1.5 |
|
|
61
|
+
| `no-exclusive-language` | Tech jargon and culturally insensitive words (blacklist, master/slave, spirit animal) | - |
|
|
62
|
+
| `no-gendered-language` | Gendered pronouns when the gender is unknown (he/she, his or her, mum and dad) | - |
|
|
63
|
+
| `no-unexplained-abbreviation` | Short words or acronyms used before you explain what they mean | 3.1.4 |
|
|
64
|
+
| `no-vague-cta` | Confusing link or button text ("click here", "read more", "here") | 2.4.4 |
|
|
65
|
+
| `no-vague-error-message` | Error messages that do not explain what is wrong ("An error occurred") | 3.3.1 |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Native Mobile: summary
|
|
70
|
+
|
|
71
|
+
`neighbor` includes strict rule implementations translated directly from our core web libraries for native iOS (SwiftUI) and Android (Jetpack Compose). They flag issues like missing Roles, unscaled text, broken touch targets, and improper semantics directly inside Xcode and Android Studio.
|
|
72
|
+
|
|
73
|
+
- **[iOS Rules](apps/ios-app/README.md):** 8 Custom SwiftLint Rules for SwiftUI (via `.swiftlint.yml`).
|
|
74
|
+
- **[Android Rules](apps/android-app/README.md):** 17 Custom Android Lint Rules for Jetpack Compose (via standard Gradle module).
|