@a11yfred/neighbor 0.3.0 → 1.0.3
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 +59 -7
- package/CONTRIBUTING.md +10 -10
- package/README.md +196 -32
- package/RULES-CONTENT.md +296 -0
- package/RULES-CSS.md +61 -0
- package/RULES-MARKUP.md +156 -0
- package/RULES.md +55 -0
- package/lib/content-rules.js +858 -0
- package/lib/helpers-angular.js +146 -146
- package/lib/helpers-jsx.js +193 -193
- package/lib/helpers-vue.js +151 -151
- package/lib/helpers.js +37 -37
- package/lib/rules.js +2413 -2413
- package/lib/ulam-rules.js +301 -301
- package/neighbor-content.mjs +80 -0
- package/neighbor-eslint-angular.mjs +68 -68
- package/neighbor-eslint-vue.mjs +48 -48
- package/neighbor-eslint.mjs +56 -56
- package/neighbor-stylelint.mjs +282 -256
- package/package.json +30 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 1.0.0 - 2026-05-12
|
|
4
|
+
|
|
5
|
+
### Breaking change
|
|
6
|
+
|
|
7
|
+
CSS rules renamed from `ulam/` to `neighbor/` namespace:
|
|
8
|
+
|
|
9
|
+
| Old | New |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `ulam/user-preferences` | `neighbor/user-preferences` |
|
|
12
|
+
| `ulam/no-outline-none` | `neighbor/no-outline-none` |
|
|
13
|
+
| `ulam/no-forced-colors-none` | `neighbor/no-forced-colors-none` |
|
|
14
|
+
|
|
15
|
+
Update your `.stylelintrc.json` to use the new names.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 0.4.0 - 2026-05-12
|
|
20
|
+
|
|
21
|
+
### New entry point
|
|
22
|
+
|
|
23
|
+
`@a11yfred/neighbor/content` - an ESLint plugin for accessibility and inclusion problems in web and app copy. Lints string literals and JSX text in JS/TS/JSX/TSX files.
|
|
24
|
+
|
|
25
|
+
### New content rules (all `warn`)
|
|
26
|
+
|
|
27
|
+
| Rule | What it flags |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `no-ableist-language` | Slurs, suffering-framing, and condescending euphemisms when writing about disability ("wheelchair-bound", "suffers from", "special needs", "differently abled") |
|
|
30
|
+
| `no-disability-metaphor` | Figurative uses of disability language ("blind spot", "tone deaf", "paralyzed by", "crippling debt") |
|
|
31
|
+
| `no-english-idiom` | English idioms and sports metaphors opaque to ESL and international readers ("slam dunk", "boil the ocean", "circle back", "touch base") |
|
|
32
|
+
| `no-vague-cta` | Vague link and button text ("click here", "read more", "here", "learn more") |
|
|
33
|
+
| `no-directional-language` | Layout-dependent position instructions ("see above", "in the right sidebar", "as shown below") |
|
|
34
|
+
| `no-unexplained-abbreviation` | Acronyms used without a prior expansion in the same file |
|
|
35
|
+
| `no-all-caps-prose` | ALL CAPS words that screen readers may spell out letter-by-letter |
|
|
36
|
+
| `no-vague-error-message` | Error messages that don't explain what went wrong ("An error occurred", "Something went wrong") |
|
|
37
|
+
| `no-ampersand-in-prose` | `&` in place of "and" in prose - announced inconsistently across AT vendors |
|
|
38
|
+
|
|
39
|
+
Rules are synthesised from 17 sources spanning W3C WAI, government plain language guides (US, UK, Australia, Canada), and disability language authorities (NCDJ, AP Stylebook, ADA National Network, APA Style, SIGACCESS). See [RULES-CONTENT.md](RULES-CONTENT.md) for full methodology and source citations.
|
|
40
|
+
|
|
41
|
+
### New rule reference pages
|
|
42
|
+
|
|
43
|
+
RULES.md is now an index. Full references split into:
|
|
44
|
+
|
|
45
|
+
- [RULES-MARKUP.md](RULES-MARKUP.md) - ESLint markup rules
|
|
46
|
+
- [RULES-CSS.md](RULES-CSS.md) - Stylelint CSS rules
|
|
47
|
+
- [RULES-CONTENT.md](RULES-CONTENT.md) - content rules with sources and methodology
|
|
48
|
+
|
|
49
|
+
### Entry point table update
|
|
50
|
+
|
|
51
|
+
`@a11yfred/neighbor/stylelint` added as an explicit stylelint alias alongside the default export.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 0.3.0 - 2026-05-12
|
|
4
56
|
|
|
5
57
|
### New rule
|
|
6
58
|
|
|
7
59
|
| Rule | What it catches |
|
|
8
|
-
|
|
9
|
-
| `
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `neighbor/no-forced-colors-none` | `forced-color-adjust: none` inside `@media (forced-colors)` - actively opts out of Windows High Contrast Mode |
|
|
10
62
|
|
|
11
63
|
### Severity changes
|
|
12
64
|
|
|
13
|
-
10 rules moved from `warn` to `off` in the recommended config
|
|
65
|
+
10 rules moved from `warn` to `off` in the recommended config - they flag real problems but are too noisy for most codebases by default. All remain available to opt in individually:
|
|
14
66
|
|
|
15
67
|
`no-application-role`, `no-grid-role`, `no-aria-roledescription`, `no-aria-readonly`, `no-tab-without-controls`, `no-href-hash`, `warn-role-alert`, `prefer-aria-disabled`, `no-target-blank-without-label`, `no-dialog-without-close`
|
|
16
68
|
|
|
@@ -25,7 +77,7 @@
|
|
|
25
77
|
|
|
26
78
|
---
|
|
27
79
|
|
|
28
|
-
## 0.2.0
|
|
80
|
+
## 0.2.0 - 2026-05-12
|
|
29
81
|
|
|
30
82
|
### New rules
|
|
31
83
|
|
|
@@ -40,7 +92,7 @@ All four rules run on React, Vue, and Angular.
|
|
|
40
92
|
|
|
41
93
|
### Extended rules
|
|
42
94
|
|
|
43
|
-
**`no-announce-in-render`** now runs in the Vue and Angular plugins, not just React. Safe contexts are tuned per framework
|
|
95
|
+
**`no-announce-in-render`** now runs in the Vue and Angular plugins, not just React. Safe contexts are tuned per framework - Vue recognises `onMounted`, `watch`, `watchEffect`, `nextTick`; Angular recognises `ngOnInit`, `ngAfterViewInit`, `ngOnChanges`, and class method event handlers.
|
|
44
96
|
|
|
45
97
|
### Setup improvements
|
|
46
98
|
|
|
@@ -48,6 +100,6 @@ README now includes correct parser snippets for Vue and Angular, and separate se
|
|
|
48
100
|
|
|
49
101
|
---
|
|
50
102
|
|
|
51
|
-
## 0.1.0
|
|
103
|
+
## 0.1.0 - 2026-04-30
|
|
52
104
|
|
|
53
105
|
Initial release.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Contributing to @a11yfred/neighbor
|
|
2
2
|
|
|
3
|
-
Neighbor is maintained by [@a11yfred](https://github.com/a11yfred). Contributions are welcome from the accessibility community
|
|
3
|
+
Neighbor is maintained by [@a11yfred](https://github.com/a11yfred). Contributions are welcome from the accessibility community - practitioners, AT users, spec readers, and people who have found a gap in existing tooling.
|
|
4
4
|
|
|
5
5
|
## What belongs here
|
|
6
6
|
|
|
7
7
|
A rule belongs in neighbor if it meets all three criteria:
|
|
8
8
|
|
|
9
|
-
1. **Statically detectable**
|
|
10
|
-
2. **Not already covered**
|
|
11
|
-
3. **Expert-backed**
|
|
9
|
+
1. **Statically detectable** - the violation can be identified from markup/code alone, without a browser or AT. Runtime-only failures (color contrast, focus order in the DOM) belong in axe-core.
|
|
10
|
+
2. **Not already covered** - jsx-a11y, vuejs-accessibility, @angular-eslint/template, or axe-core doesn't already flag it in a recommended config.
|
|
11
|
+
3. **Expert-backed** - there's a WCAG SC, ARIA spec citation, or clear consensus from accessibility practitioners (Roselli, O'Hara, Lauke, Sutton, Pickering, Groves, Eggert, etc.).
|
|
12
12
|
|
|
13
13
|
If you're unsure, open an issue before writing a rule. A brief description and a source is enough to start a conversation.
|
|
14
14
|
|
|
15
15
|
## What doesn't belong here
|
|
16
16
|
|
|
17
17
|
- Rules that require runtime information (computed styles, DOM layout, AT output)
|
|
18
|
-
- Rules already in jsx-a11y recommended
|
|
18
|
+
- Rules already in jsx-a11y recommended - neighbor extends it, not replaces it
|
|
19
19
|
- Opinionated style rules without a clear accessibility impact
|
|
20
20
|
- Rules with very high false-positive rates on real codebases (see the rejected rules list in [RULES.md](RULES.md))
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ cd neighbor
|
|
|
27
27
|
npm install
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
No build step. Rules are plain ES modules
|
|
30
|
+
No build step. Rules are plain ES modules - edit and run ESLint directly.
|
|
31
31
|
|
|
32
32
|
## How rules are structured
|
|
33
33
|
|
|
@@ -62,7 +62,7 @@ The `h` adapter gives you a uniform interface across all three frameworks:
|
|
|
62
62
|
| `h.getElementName(node)` | lowercase tag name, or `null` for custom components |
|
|
63
63
|
| `h.hasAttr(node, name)` | boolean |
|
|
64
64
|
| `h.getRoleValue(node)` | role string or `null` |
|
|
65
|
-
| `h.hasAccessibleName(node)` | boolean
|
|
65
|
+
| `h.hasAccessibleName(node)` | boolean - checks `aria-label` / `aria-labelledby` |
|
|
66
66
|
| `h.isInteractiveElement(node)` | boolean |
|
|
67
67
|
| `h.getParent(node)` | parent element node or `null` |
|
|
68
68
|
| `h.getAncestors(node)` | iterable of ancestor element nodes, root-ward |
|
|
@@ -71,7 +71,7 @@ The `h` adapter gives you a uniform interface across all three frameworks:
|
|
|
71
71
|
| `h.elementVisitor` | AST node type string for `create()` visitor key |
|
|
72
72
|
| `h.elementWithChildrenVisitor` | visitor key for rules that need child access |
|
|
73
73
|
|
|
74
|
-
**Angular caveat:** `getParent()` and `getAncestors()` return `null`/nothing for Angular
|
|
74
|
+
**Angular caveat:** `getParent()` and `getAncestors()` return `null`/nothing for Angular - the template parser doesn't attach parent pointers. Rules that require ancestor walking should degrade gracefully (skip the check, don't throw).
|
|
75
75
|
|
|
76
76
|
After writing your factory:
|
|
77
77
|
|
|
@@ -85,7 +85,7 @@ Stylelint rules live in [`neighbor-stylelint.mjs`](neighbor-stylelint.mjs) and u
|
|
|
85
85
|
|
|
86
86
|
| Severity | When to use |
|
|
87
87
|
|---|---|
|
|
88
|
-
| `error` | Unambiguous AT breakage
|
|
88
|
+
| `error` | Unambiguous AT breakage - a phantom control, broken name computation, HTML spec violation. No legitimate override. |
|
|
89
89
|
| `warn` | Strong guidance with a clear accessibility basis, but real codebases occasionally have justified exceptions. |
|
|
90
90
|
| `off` | Real problem, but fires too often on legitimate patterns to be on by default. Make it available; let teams opt in. |
|
|
91
91
|
|
|
@@ -107,7 +107,7 @@ Use the PR template. The key things:
|
|
|
107
107
|
|
|
108
108
|
- **What problem does this flag?** Link a WCAG SC, ARIA spec section, or expert source.
|
|
109
109
|
- **Why can't axe-core catch it at runtime instead?** (If it can, it probably belongs there.)
|
|
110
|
-
- **What are the false-positive cases?** Be honest
|
|
110
|
+
- **What are the false-positive cases?** Be honest - we'd rather move a rule to `off` than reject it.
|
|
111
111
|
- **Does it degrade gracefully for Angular?** (Parent walking unavailable.)
|
|
112
112
|
|
|
113
113
|
## Questions
|
package/README.md
CHANGED
|
@@ -2,26 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Neighbor is an accessibility linting plugin for ESLint and Stylelint that builds on jsx-a11y. It looks to cover gaps: bad ARIA patterns, live region misuse, missing names on roles, and CSS that removes focus indicators. It also brings that coverage to Vue and Angular, where jsx-a11y does not apply.
|
|
4
4
|
|
|
5
|
-
Some rules are specific to **@ulam**
|
|
5
|
+
Some rules are specific to **@ulam** - an upcoming JavaScript framework by the same author. Those rules are prefixed `no-announce-in-render`, `no-hash-router-in-remix`, and `no-use-page-title-in-remix`. They activate only when @ulam-related imports are detected and are harmless in non-@ulam projects.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
9
|
- [Install](#install)
|
|
10
10
|
- [Entry points](#entry-points)
|
|
11
11
|
- [Setup](#setup)
|
|
12
|
-
- [
|
|
12
|
+
- [Vanilla JS / plain HTML (no framework)](#vanilla-js--plain-html-no-framework)
|
|
13
|
+
- [React / JSX](#react-jsx)
|
|
13
14
|
- [Remix 2](#remix-2)
|
|
14
15
|
- [Remix 3](#remix-3)
|
|
15
16
|
- [Vue](#vue)
|
|
16
17
|
- [Angular](#angular)
|
|
17
18
|
- [Stylelint](#stylelint)
|
|
19
|
+
- [Content linting](#content-linting)
|
|
18
20
|
- [Peer dependencies](#peer-dependencies)
|
|
19
21
|
- [What neighbor adds](#what-neighbor-adds)
|
|
20
|
-
- [ESLint
|
|
21
|
-
- [ESLint
|
|
22
|
-
- [ESLint
|
|
23
|
-
- [ESLint
|
|
24
|
-
- [Stylelint
|
|
22
|
+
- [ESLint - React / JSX](#eslint-react-jsx)
|
|
23
|
+
- [ESLint - Remix 2](#eslint-remix-2)
|
|
24
|
+
- [ESLint - Vue SFCs](#eslint-vue-sfcs)
|
|
25
|
+
- [ESLint - Angular templates](#eslint-angular-templates)
|
|
26
|
+
- [Stylelint - CSS](#stylelint-css)
|
|
27
|
+
- [Content linter](#content-linter)
|
|
25
28
|
- [Rule severity](#rule-severity)
|
|
26
29
|
- [Contributing](CONTRIBUTING.md)
|
|
27
30
|
- [See also](#see-also)
|
|
@@ -37,13 +40,108 @@ npm install --save-dev @a11yfred/neighbor
|
|
|
37
40
|
|
|
38
41
|
| Import | Use for |
|
|
39
42
|
| --- | --- |
|
|
40
|
-
| `@a11yfred/neighbor/eslint` | React / JSX, Remix 2 |
|
|
41
|
-
| `@a11yfred/neighbor/eslint-vue` | Vue SFCs |
|
|
42
|
-
| `@a11yfred/neighbor/eslint-angular` | Angular templates |
|
|
43
|
-
| `@a11yfred/neighbor` |
|
|
43
|
+
| `@a11yfred/neighbor/eslint` | React / JSX, Remix 2 - markup rules |
|
|
44
|
+
| `@a11yfred/neighbor/eslint-vue` | Vue SFCs - markup rules |
|
|
45
|
+
| `@a11yfred/neighbor/eslint-angular` | Angular templates - markup rules |
|
|
46
|
+
| `@a11yfred/neighbor/content` | Any JS/TS/JSX/TSX - content and prose rules |
|
|
47
|
+
| `@a11yfred/neighbor` | Stylelint - CSS rules (default export) |
|
|
48
|
+
| `@a11yfred/neighbor/stylelint` | Stylelint - CSS rules (explicit) |
|
|
44
49
|
|
|
45
50
|
## Setup
|
|
46
51
|
|
|
52
|
+
### Vanilla JS / plain HTML (no framework)
|
|
53
|
+
|
|
54
|
+
If you write plain JavaScript with no JSX, React, Vue, or Angular, only the **Stylelint CSS rules** and the **content linter** apply. The ESLint markup rules require a component framework — they lint JSX or template syntax that plain JS does not have.
|
|
55
|
+
|
|
56
|
+
**What you get:**
|
|
57
|
+
|
|
58
|
+
| Plugin | What it checks |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| Stylelint (`@a11yfred/neighbor`) | CSS: bare `outline: none`, forced-colors opt-out, motion/transparency without `prefers-*` fallbacks |
|
|
61
|
+
| Content linter (`@a11yfred/neighbor/content`) | JS strings: ableist language, vague CTAs, unexplained abbreviations, idioms, all-caps prose |
|
|
62
|
+
|
|
63
|
+
**Stylelint setup** (CSS only, no framework needed):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install --save-dev stylelint stylelint-config-standard @a11yfred/neighbor
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
// .stylelintrc.json
|
|
71
|
+
{
|
|
72
|
+
"extends": ["stylelint-config-standard"],
|
|
73
|
+
"plugins": ["@a11yfred/neighbor"],
|
|
74
|
+
"rules": {
|
|
75
|
+
"neighbor/no-outline-none": true,
|
|
76
|
+
"neighbor/no-forced-colors-none": true,
|
|
77
|
+
"neighbor/user-preferences": true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Run it:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx stylelint "**/*.css"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Content linter setup** (plain JS string literals, no framework needed):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install --save-dev eslint @a11yfred/neighbor
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
// eslint.config.js (ESLint flat config, ESLint >= 8)
|
|
96
|
+
import neighborContent from '@a11yfred/neighbor/content'
|
|
97
|
+
|
|
98
|
+
export default [
|
|
99
|
+
{
|
|
100
|
+
files: ['**/*.js'],
|
|
101
|
+
plugins: { ...neighborContent.configs.recommended.plugins },
|
|
102
|
+
rules: { ...neighborContent.configs.recommended.rules },
|
|
103
|
+
},
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Run it:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx eslint src/
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Both together:**
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// eslint.config.js
|
|
117
|
+
import neighborContent from '@a11yfred/neighbor/content'
|
|
118
|
+
|
|
119
|
+
export default [
|
|
120
|
+
{
|
|
121
|
+
files: ['**/*.js'],
|
|
122
|
+
plugins: { ...neighborContent.configs.recommended.plugins },
|
|
123
|
+
rules: { ...neighborContent.configs.recommended.rules },
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
// .stylelintrc.json
|
|
130
|
+
{
|
|
131
|
+
"extends": ["stylelint-config-standard"],
|
|
132
|
+
"plugins": ["@a11yfred/neighbor"],
|
|
133
|
+
"rules": {
|
|
134
|
+
"neighbor/no-outline-none": true,
|
|
135
|
+
"neighbor/no-forced-colors-none": true,
|
|
136
|
+
"neighbor/user-preferences": true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The ESLint markup rules (`@a11yfred/neighbor/eslint` and variants) require JSX or a component template syntax. They will not produce useful output on plain HTML or vanilla JS files and should be skipped.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
47
145
|
### React / JSX
|
|
48
146
|
|
|
49
147
|
Neighbor works alongside `eslint-plugin-jsx-a11y`. Install both.
|
|
@@ -87,7 +185,7 @@ export default [
|
|
|
87
185
|
|
|
88
186
|
### Remix 3
|
|
89
187
|
|
|
90
|
-
Remix 3 is framework-agnostic and does not require React. Neighbor does not have a dedicated Remix 3 entry point
|
|
188
|
+
Remix 3 is framework-agnostic and does not require React. Neighbor does not have a dedicated Remix 3 entry point - use the entry point that matches your renderer.
|
|
91
189
|
|
|
92
190
|
If you are using React with Remix 3:
|
|
93
191
|
|
|
@@ -159,23 +257,67 @@ export default [
|
|
|
159
257
|
|
|
160
258
|
### Stylelint
|
|
161
259
|
|
|
162
|
-
```
|
|
260
|
+
```json
|
|
163
261
|
// .stylelintrc.json
|
|
164
262
|
{
|
|
165
263
|
"plugins": ["@a11yfred/neighbor"],
|
|
166
264
|
"rules": {
|
|
167
|
-
"
|
|
168
|
-
"
|
|
265
|
+
"neighbor/user-preferences": true,
|
|
266
|
+
"neighbor/no-outline-none": true,
|
|
267
|
+
"neighbor/no-forced-colors-none": true
|
|
169
268
|
}
|
|
170
269
|
}
|
|
171
270
|
```
|
|
172
271
|
|
|
272
|
+
### Content linting
|
|
273
|
+
|
|
274
|
+
The content plugin lints string literals and JSX text in JavaScript, TypeScript, JSX, and TSX files. It is separate from the markup plugins and can be used alongside any of them.
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npm install --save-dev @a11yfred/neighbor
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
// eslint.config.js
|
|
282
|
+
import neighborContent from '@a11yfred/neighbor/content'
|
|
283
|
+
|
|
284
|
+
export default [
|
|
285
|
+
{
|
|
286
|
+
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
287
|
+
plugins: { ...neighborContent.configs.recommended.plugins },
|
|
288
|
+
rules: { ...neighborContent.configs.recommended.rules },
|
|
289
|
+
},
|
|
290
|
+
]
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
To use alongside the React markup plugin:
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
// eslint.config.js
|
|
297
|
+
import neighbor from '@a11yfred/neighbor/eslint'
|
|
298
|
+
import neighborContent from '@a11yfred/neighbor/content'
|
|
299
|
+
|
|
300
|
+
export default [
|
|
301
|
+
{
|
|
302
|
+
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
303
|
+
plugins: {
|
|
304
|
+
...neighbor.configs.recommended.plugins,
|
|
305
|
+
...neighborContent.configs.recommended.plugins,
|
|
306
|
+
},
|
|
307
|
+
rules: {
|
|
308
|
+
...neighbor.configs.recommended.rules,
|
|
309
|
+
...neighborContent.configs.recommended.rules,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
]
|
|
313
|
+
```
|
|
314
|
+
|
|
173
315
|
## Peer dependencies
|
|
174
316
|
|
|
175
317
|
| Peer | Required for |
|
|
176
318
|
| --- | --- |
|
|
177
319
|
| `eslint >= 8` | Any ESLint entry point |
|
|
178
|
-
| `eslint-plugin-jsx-a11y >= 6` | React config
|
|
320
|
+
| `eslint-plugin-jsx-a11y >= 6` | React config - neighbor extends it, not replaces it |
|
|
179
321
|
| `eslint-plugin-vuejs-accessibility >= 2` | Vue config |
|
|
180
322
|
| `@angular-eslint/eslint-plugin-template >= 17` | Angular config |
|
|
181
323
|
| `stylelint >= 14` | Stylelint config |
|
|
@@ -184,7 +326,7 @@ All peers are optional. Install only what your project uses.
|
|
|
184
326
|
|
|
185
327
|
## What neighbor adds
|
|
186
328
|
|
|
187
|
-
### ESLint
|
|
329
|
+
### ESLint - React / JSX
|
|
188
330
|
|
|
189
331
|
Base: `eslint-plugin-jsx-a11y`
|
|
190
332
|
|
|
@@ -198,8 +340,8 @@ Base: `eslint-plugin-jsx-a11y`
|
|
|
198
340
|
| `role="dialog"` requires accessible name | `no-roles-without-name` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
199
341
|
| `role="group"` with form controls requires name | `no-group-without-name` | [1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
200
342
|
| `role="tooltip"` requires `id` on the tooltip | `no-tooltip-role-misuse` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
201
|
-
| `role="application"` disables AT browse mode | `no-application-role` |
|
|
202
|
-
| `role="grid"` almost always wrong | `no-grid-role` |
|
|
343
|
+
| `role="application"` disables AT browse mode | `no-application-role` | - |
|
|
344
|
+
| `role="grid"` almost always wrong | `no-grid-role` | - |
|
|
203
345
|
| `role="menu"` on nav triggers wrong AT mode | `no-menu-role-on-nav` | [2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
204
346
|
| `role="presentation"` on a focusable element | `no-presentation-on-focusable` | [2.1.1](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
|
|
205
347
|
| `role="log"` must not contain interactive children | `no-log-with-interactive-children` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
@@ -212,9 +354,9 @@ Base: `eslint-plugin-jsx-a11y`
|
|
|
212
354
|
| `role="listbox"` requires `role="option"` children | `no-listbox-without-option` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
213
355
|
| `role="tree"` requires `role="treeitem"` children | `no-tree-without-treeitem` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
214
356
|
| `role="feed"` requires `role="article"` children | `no-feed-without-article` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
215
|
-
| `aria-hidden="true"` + `role="none"` is redundant | `no-redundant-aria-hidden-with-presentation` |
|
|
216
|
-
| `aria-roledescription` does not translate | `no-aria-roledescription` |
|
|
217
|
-
| `aria-readonly` has poor AT support | `no-aria-readonly` |
|
|
357
|
+
| `aria-hidden="true"` + `role="none"` is redundant | `no-redundant-aria-hidden-with-presentation` | - |
|
|
358
|
+
| `aria-roledescription` does not translate | `no-aria-roledescription` | - |
|
|
359
|
+
| `aria-readonly` has poor AT support | `no-aria-readonly` | - |
|
|
218
360
|
| `aria-owns` on a void element | `no-aria-owns-on-void` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
219
361
|
| `aria-activedescendant` requires a non-empty static ID | `no-aria-activedescendant-without-id` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
220
362
|
| `aria-required` only valid on form-control roles | `no-aria-required-on-non-form` | [4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
|
|
@@ -238,7 +380,7 @@ Base: `eslint-plugin-jsx-a11y`
|
|
|
238
380
|
| `<th>` or header role with no accessible text | `no-empty-table-header` | [1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
239
381
|
| `announce()` called in component render body | `no-announce-in-render` | [4.1.3](https://www.w3.org/WAI/WCAG21/Understanding/status-messages) |
|
|
240
382
|
|
|
241
|
-
### ESLint
|
|
383
|
+
### ESLint - Remix 2
|
|
242
384
|
|
|
243
385
|
Same as React / JSX. Additional rules activate when Remix imports are detected in the file being linted:
|
|
244
386
|
|
|
@@ -247,7 +389,7 @@ Same as React / JSX. Additional rules activate when Remix imports are detected i
|
|
|
247
389
|
| `@ulam` hash router alongside `react-router` | `no-hash-router-in-remix` | warn |
|
|
248
390
|
| `usePageTitle()` alongside `react-router` | `no-use-page-title-in-remix` | warn |
|
|
249
391
|
|
|
250
|
-
### ESLint
|
|
392
|
+
### ESLint - Vue SFCs
|
|
251
393
|
|
|
252
394
|
Base: `eslint-plugin-vuejs-accessibility`
|
|
253
395
|
|
|
@@ -266,21 +408,39 @@ Neighbor adds everything in the React table above, adapted for Vue's AST (`v-htm
|
|
|
266
408
|
| `scope` on `<td>` (only valid on `<th>`) | `no-scope-on-td` | [1.3.1](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) |
|
|
267
409
|
| `announce()` called outside `onMounted`/`watch`/handler | `no-announce-in-render` | [4.1.3](https://www.w3.org/WAI/WCAG21/Understanding/status-messages) |
|
|
268
410
|
|
|
269
|
-
### ESLint
|
|
411
|
+
### ESLint - Angular templates
|
|
270
412
|
|
|
271
413
|
Base: `@angular-eslint/eslint-plugin-template`
|
|
272
414
|
|
|
273
|
-
Neighbor adds the same rule set as Vue, adapted for Angular's template AST (`[innerHTML]` instead of `dangerouslySetInnerHTML`). The `no-announce-in-render` rule also lints Angular component TypeScript files
|
|
415
|
+
Neighbor adds the same rule set as Vue, adapted for Angular's template AST (`[innerHTML]` instead of `dangerouslySetInnerHTML`). The `no-announce-in-render` rule also lints Angular component TypeScript files - see the setup instructions for how to configure it for `.ts` files alongside `.html` templates.
|
|
274
416
|
|
|
275
417
|
**Known limitation:** Angular's template parser does not attach parent pointers to AST nodes. Rules that need to walk up the tree (`no-summary-without-details`, `no-button-type-missing`, `no-log-with-interactive-children`, `no-menu-role-on-nav`, `no-heading-inside-interactive`) will silently pass in Angular templates. The `no-dynamic-content-without-live` rule only checks the element itself for Angular (no ancestor walk).
|
|
276
418
|
|
|
277
|
-
### Stylelint
|
|
419
|
+
### Stylelint - CSS
|
|
278
420
|
|
|
279
421
|
| Rule | What it checks |
|
|
280
422
|
| --- | --- |
|
|
281
|
-
| `
|
|
282
|
-
| `
|
|
283
|
-
| `
|
|
423
|
+
| `neighbor/user-preferences` | Warns when motion, transparency, or alpha colors are used without `@media (prefers-*)` fallbacks - [SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum) / [SC 2.3.3](https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions) |
|
|
424
|
+
| `neighbor/no-outline-none` | Disallows bare `outline: none` or `outline: 0` outside `:focus` selectors - [SC 2.4.7](https://www.w3.org/WAI/WCAG21/Understanding/focus-visible) |
|
|
425
|
+
| `neighbor/no-forced-colors-none` | Disallows `forced-color-adjust: none` inside `@media (forced-colors)` - opts out of Windows High Contrast Mode - [SC 1.4.11](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast) |
|
|
426
|
+
|
|
427
|
+
### Content linter
|
|
428
|
+
|
|
429
|
+
Rules that flag accessibility and inclusion problems in web and app copy. Works on string literals and JSX text in JS/TS/JSX/TSX files.
|
|
430
|
+
|
|
431
|
+
| Rule | What it flags | Severity | WCAG SC |
|
|
432
|
+
| --- | --- | --- | --- |
|
|
433
|
+
| `no-ableist-language` | Slurs, condescending euphemisms, suffering-framing ("suffers from", "wheelchair-bound", "special needs") | warn | [3.1.1](https://www.w3.org/WAI/WCAG22/Understanding/language-of-page) |
|
|
434
|
+
| `no-disability-metaphor` | Figurative use of disability language ("blind spot", "tone deaf", "paralyzed by") | warn | - |
|
|
435
|
+
| `no-english-idiom` | Idioms and sports metaphors opaque to ESL readers ("ball park", "slam dunk", "boil the ocean") | warn | [3.1.5](https://www.w3.org/WAI/WCAG22/Understanding/reading-level) |
|
|
436
|
+
| `no-vague-cta` | Vague link and button text ("click here", "read more", "here") | warn | [2.4.4](https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context) |
|
|
437
|
+
| `no-directional-language` | Layout-dependent position references ("see above", "in the right sidebar") | warn | [1.3.3](https://www.w3.org/WAI/WCAG22/Understanding/sensory-characteristics) |
|
|
438
|
+
| `no-unexplained-abbreviation` | Acronyms used without a prior expansion in the same file | warn | [3.1.4](https://www.w3.org/WAI/WCAG22/Understanding/abbreviations) |
|
|
439
|
+
| `no-all-caps-prose` | ALL CAPS words in prose that screen readers may spell out letter-by-letter | warn | - |
|
|
440
|
+
| `no-vague-error-message` | Error messages that don't explain what went wrong ("An error occurred", "Something went wrong") | warn | [3.3.1](https://www.w3.org/WAI/WCAG22/Understanding/error-identification) |
|
|
441
|
+
| `no-ampersand-in-prose` | `&` used in place of "and" in prose - announced inconsistently by screen readers | warn | - |
|
|
442
|
+
|
|
443
|
+
See [RULES-CONTENT.md](RULES-CONTENT.md) for the full rule reference including sources, methodology, and the language-evolution note.
|
|
284
444
|
|
|
285
445
|
## Rule severity
|
|
286
446
|
|
|
@@ -288,13 +448,17 @@ Neighbor adds the same rule set as Vue, adapted for Angular's template AST (`[in
|
|
|
288
448
|
| --- | --- |
|
|
289
449
|
| `error` | Definite AT breakage or HTML spec violation |
|
|
290
450
|
| `warn` | Strong guidance, occasional legitimate overrides exist |
|
|
291
|
-
| `off` | Available but disabled
|
|
451
|
+
| `off` | Available but disabled - too noisy for most codebases, enable if it fits your project |
|
|
292
452
|
|
|
293
453
|
All rules can be overridden in your config.
|
|
294
454
|
|
|
295
455
|
## See also
|
|
296
456
|
|
|
297
|
-
- [RULES.md](RULES.md)
|
|
457
|
+
- [RULES.md](RULES.md) - rule index across all domains
|
|
458
|
+
- [RULES-MARKUP.md](RULES-MARKUP.md) - full ESLint rule reference (markup)
|
|
459
|
+
- [RULES-CSS.md](RULES-CSS.md) - full Stylelint rule reference (CSS)
|
|
460
|
+
- [RULES-CONTENT.md](RULES-CONTENT.md) - full content rule reference with sources
|
|
461
|
+
- [neighbor-vale](https://github.com/a11yfred/neighbor-vale) - companion Vale package for prose linting in Markdown, MDX, and HTML
|
|
298
462
|
|
|
299
463
|
## License
|
|
300
464
|
|