@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 CHANGED
@@ -1,16 +1,68 @@
1
1
  # Changelog
2
2
 
3
- ## 0.3.02026-05-12
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
- | `ulam/no-forced-colors-none` | `forced-color-adjust: none` inside `@media (forced-colors)`actively opts out of Windows High Contrast Mode |
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 configthey flag real problems but are too noisy for most codebases by default. All remain available to opt in individually:
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.02026-05-12
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 frameworkVue recognises `onMounted`, `watch`, `watchEffect`, `nextTick`; Angular recognises `ngOnInit`, `ngAfterViewInit`, `ngOnChanges`, and class method event handlers.
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.02026-04-30
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 communitypractitioners, AT users, spec readers, and people who have found a gap in existing tooling.
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**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.).
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 recommendedneighbor extends it, not replaces it
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 modulesedit and run ESLint directly.
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)` | booleanchecks `aria-label` / `aria-labelledby` |
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 Angularthe template parser doesn't attach parent pointers. Rules that require ancestor walking should degrade gracefully (skip the check, don't throw).
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 breakagea phantom control, broken name computation, HTML spec violation. No legitimate override. |
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 honestwe'd rather move a rule to `off` than reject it.
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**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.
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
- - [React / JSX](#react--jsx)
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
- - [ESLintReact / JSX](#eslint--react--jsx)
21
- - [ESLintRemix 2](#eslint--remix-2)
22
- - [ESLintVue SFCs](#eslint--vue-sfcs)
23
- - [ESLintAngular templates](#eslint--angular-templates)
24
- - [StylelintCSS](#stylelint--css)
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` | Stylelint CSS user-preference fallbacks |
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 pointuse the entry point that matches your renderer.
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
- ```js
260
+ ```json
163
261
  // .stylelintrc.json
164
262
  {
165
263
  "plugins": ["@a11yfred/neighbor"],
166
264
  "rules": {
167
- "ulam/user-preferences": true,
168
- "ulam/no-outline-none": true
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 configneighbor extends it, not replaces it |
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
- ### ESLintReact / JSX
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
- ### ESLintRemix 2
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
- ### ESLintVue SFCs
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
- ### ESLintAngular templates
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 filessee the setup instructions for how to configure it for `.ts` files alongside `.html` templates.
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
- ### StylelintCSS
419
+ ### Stylelint - CSS
278
420
 
279
421
  | Rule | What it checks |
280
422
  | --- | --- |
281
- | `ulam/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) |
282
- | `ulam/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) |
283
- | `ulam/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) |
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 disabledtoo noisy for most codebases, enable if it fits your project |
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) full rule list with descriptions
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