@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 CHANGED
@@ -1,41 +1,74 @@
1
1
  # Changelog
2
2
 
3
- ## 1.1.0 - 2026-05-23
4
-
5
- ### New rules
6
-
7
- | Rule | What it catches |
8
- | --- | --- |
9
- | `no-disabled-and-aria-disabled` | Elements with both `disabled` and `aria-disabled` attributes - causes conflicting states in assistive tech |
10
-
11
- ### Severity changes
12
-
13
- - `prefer-aria-disabled` moved from `off` to `error` by default to enforce discoverable form controls in tab order.
14
-
15
- ---
16
-
17
- ## 1.0.6 - 2026-05-13
18
-
19
- Add Severity column to all rule tables in README.
3
+ <!-- markdownlint-disable MD024 -->
4
+
5
+ All notable changes to this project will be documented in this file.
6
+
7
+ ## [2.0.0] - 2026-05-24
8
+
9
+ ### Added
10
+
11
+ - **Native iOS Linting (SwiftUI)**: 9 custom SwiftLint rules for SwiftUI accessibility, distributed via `.swiftlint.yml`. Checks for disabled focus effects, hardcoded font sizes, redundant accessibility labels, small touch targets, restrictive orientation locks, and more. See [apps/ios-app/README.md](apps/ios-app/README.md).
12
+ - **Native Android Linting (Jetpack Compose)**: 8 custom Android Lint detector rules for Jetpack Compose, distributed as a standard Gradle lint module. Checks for missing `contentDescription`, hardcoded `sp` text, small touch targets, missing `stateDescription`, `pointerInput` without semantics, disabled traversal groups, restrictive orientation, and forced light mode. See [apps/android-app/README.md](apps/android-app/README.md).
13
+ - **Microsoft Word Add-in**: An Office.js add-in that checks Word documents for exclusionary and ableist language while you type. Built with React, Fluent UI, and Vite. See [apps/word-addin/README.md](apps/word-addin/README.md).
14
+ - **New Stylelint Rules**:
15
+ - `neighbor/no-text-justify` (error): Disallows `text-align: justify` which creates uneven word spacing that is hard for dyslexic users to read (SC 1.4.8).
16
+ - `neighbor/no-absolute-viewport-text` (warn): Warns on pure viewport units (`vw`, `vh`) for text sizing which block browser zoom (SC 1.4.4).
17
+ - `neighbor/no-user-select-all-none` (warn): Warns on `user-select: none` which prevents text selection and translation (SC 1.4.4).
18
+ - **New Content Rules**:
19
+ - `no-exclusive-language`: Tech jargon and culturally appropriated terms (blacklist, master/slave, sanity check, spirit animal).
20
+ - `no-colonial-and-violent-language`: Words rooted in colonialism or violence applied to people (stakeholder, target population, tackle).
21
+ - `no-deficit-language`: Words that reduce people to their circumstances (the homeless, inmate, addict, at-risk youth).
22
+ - `no-gendered-language`: Gendered pronouns when gender is unknown (he/she, his or her, mum and dad).
23
+ - `no-anti-lgbtq-language`: Outdated or pathologizing terms about sexual orientation and gender identity.
24
+ - `no-device-specific-action`: Device-specific input actions (click, tap, swipe).
25
+ - **New Markup Rules**:
26
+ - `vue-router-focus-management` (off): Warns if a Vue Router view transition happens without managing focus.
27
+ - `react-spa-focus-management` (warn): Warns if a React Router or Remix transition happens without managing focus.
28
+ - **Remix 3 Support**: Added a dedicated ESLint configuration (`@a11yfred/neighbor/remix3`) for file-based routing.
29
+ - **Web Components / Vanilla HTML Support**: Added a dedicated ESLint configuration (`@a11yfred/neighbor/webcomponents`) using `@html-eslint/parser`.
30
+ - **App Stubs**: Scaffolded workspace directories for `webapp`, `chrome-extension`, `firefox-extension`, and `electron-app`.
31
+ - **Vale Dictionary**: Compiled Vale-compatible dictionary containing textlint content vocabulary for standalone Markdown checking.
32
+
33
+ ### Changed
34
+
35
+ - **Rule Severities**: All recommended framework ARIA rules and Vale content rules are now set to `warn` by default to reduce friction on initial adoption.
36
+ - **Rule Severities**: The `prefer-aria-disabled` rule is explicitly set to `error`, as previously documented.
37
+ - **Linter Dependencies**: Dropped `eslint-plugin-jsx-a11y` as a direct dependency. It is now an optional `peerDependency`, meaning teams using only Web Components or text linting are not forced to install React accessibility rules. `eslint-plugin-vuejs-accessibility` and `@angular-eslint/eslint-plugin-template` have also been properly registered as optional.
38
+ - **Vale Configs**: Automatically generated and packaged the latest content rules for `@a11yfred/vale-config-neighbor`.
39
+ - **CI/CD**: Fixed publish workflow to publish from `packages/neighbor/` and `packages/textlint-rule-neighbor/` instead of the monorepo root. Fixed Vale zip path.
40
+
41
+ ### Documentation
42
+
43
+ - Comprehensive 6-pass documentation audit for markdownlint compliance, accuracy, plain language, and ESL friendliness.
44
+ - All rule tables sorted by severity (most severe first) then alphabetically.
45
+ - Contractions replaced with full forms across all authored documentation.
46
+ - All ecosystem tools (ESLint, Stylelint, textlint, Vale, SwiftLint, Android Lint, Word Add-in) documented in README.
47
+ - Grammatical em dashes and en dashes replaced with hyphens across the entire codebase.
48
+ - AI-assisted development disclaimer added to README.
49
+
50
+ ## 1.1.0 - 2026-05-23
51
+
52
+ - **New rule:** `no-disabled-and-aria-disabled`: Elements with both `disabled` and `aria-disabled` attributes cause conflicting states in assistive tech
53
+ - **Severity change:** `prefer-aria-disabled` moved from `off` to `error` by default to enforce discoverable form controls in tab order.
20
54
 
21
55
  ---
22
56
 
23
- ## 1.0.5 - 2026-05-13
57
+ ## 1.0.6 - 2026-05-13
24
58
 
25
- Docs cleanup: remove em dashes, fix MD036/MD040 markdownlint issues across all docs.
59
+ **Changed:**
26
60
 
27
- ---
28
-
29
- ## 1.0.4 - 2026-05-13
61
+ - **Documentation:** Add severity column to all rule tables in README
62
+ - **Docs cleanup:** Remove em dashes, fix MD036/MD040 markdownlint issues across all docs
30
63
 
31
- ### Bug fixes
64
+ **Fixed:**
32
65
 
33
66
  - **`no-placeholder-only`**: no longer false-positives on `<input>` elements inside a `role="search"` landmark with an accessible name. The input is correctly labeled at the group level in that pattern.
34
67
  - **`no-dialog-without-close`**: no longer false-positives on `role="dialog"` elements whose children are passed dynamically (`{children}`). When a close button cannot be statically detected, the rule skips rather than reporting.
35
68
 
36
69
  ---
37
70
 
38
- ## 1.0.0 - 2026-05-12
71
+ ## 1.0.0 - 2026-05-12
39
72
 
40
73
  ### Breaking change
41
74
 
@@ -43,98 +76,56 @@ CSS rules renamed from `ulam/` to `neighbor/` namespace:
43
76
 
44
77
  | Old | New |
45
78
  | --- | --- |
46
- | `ulam/user-preferences` | `neighbor/user-preferences` |
47
- | `ulam/no-outline-none` | `neighbor/no-outline-none` |
48
79
  | `ulam/no-forced-colors-none` | `neighbor/no-forced-colors-none` |
80
+ | `ulam/no-outline-none` | `neighbor/no-outline-none` |
81
+ | `ulam/user-preferences` | `neighbor/user-preferences` |
49
82
 
50
83
  Update your `.stylelintrc.json` to use the new names.
51
84
 
52
- ---
53
-
54
- ## 0.4.0 - 2026-05-12
55
-
56
- ### New entry point
85
+ ### Added
57
86
 
58
- `@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.
59
-
60
- ### New content rules (all `warn`)
87
+ - **New rule:** `neighbor/no-forced-colors-none`: `forced-color-adjust: none` inside `@media (forced-colors)` actively opts out of Windows High Contrast Mode
88
+ - **New entry point:** `@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.
89
+ - **New content rules (all `warn`):**
61
90
 
62
91
  | Rule | What it flags |
63
92
  | --- | --- |
64
93
  | `no-ableist-language` | Slurs, suffering-framing, and condescending euphemisms when writing about disability ("wheelchair-bound", "suffers from", "special needs", "differently abled") |
94
+ | `no-all-caps-prose` | ALL CAPS words that screen readers may spell out letter-by-letter |
95
+ | `no-ampersand-in-prose` | `&` in place of "and" in prose - announced inconsistently across AT vendors |
96
+ | `no-directional-language` | Layout-dependent position instructions ("see above", "in the right sidebar", "as shown below") |
65
97
  | `no-disability-metaphor` | Figurative uses of disability language ("blind spot", "tone deaf", "paralyzed by", "crippling debt") |
66
98
  | `no-english-idiom` | English idioms and sports metaphors opaque to ESL and international readers ("slam dunk", "boil the ocean", "circle back", "touch base") |
67
- | `no-vague-cta` | Vague link and button text ("click here", "read more", "here", "learn more") |
68
- | `no-directional-language` | Layout-dependent position instructions ("see above", "in the right sidebar", "as shown below") |
69
99
  | `no-unexplained-abbreviation` | Acronyms used without a prior expansion in the same file |
70
- | `no-all-caps-prose` | ALL CAPS words that screen readers may spell out letter-by-letter |
100
+ | `no-vague-cta` | Vague link and button text ("click here", "read more", "here", "learn more") |
71
101
  | `no-vague-error-message` | Error messages that don't explain what went wrong ("An error occurred", "Something went wrong") |
72
- | `no-ampersand-in-prose` | `&` in place of "and" in prose - announced inconsistently across AT vendors |
73
102
 
74
103
  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.
75
104
 
76
- ### New rule reference pages
77
-
78
- RULES.md is now an index. Full references split into:
79
-
80
- - [RULES-MARKUP.md](RULES-MARKUP.md) - ESLint markup rules
81
- - [RULES-CSS.md](RULES-CSS.md) - Stylelint CSS rules
82
- - [RULES-CONTENT.md](RULES-CONTENT.md) - content rules with sources and methodology
83
-
84
- ### Entry point table update
85
-
86
- `@a11yfred/neighbor/stylelint` added as an explicit stylelint alias alongside the default export.
87
-
88
- ---
89
-
90
- ## 0.3.0 - 2026-05-12
91
-
92
- ### New rule
93
-
94
- | Rule | What it catches |
95
- | --- | --- |
96
- | `neighbor/no-forced-colors-none` | `forced-color-adjust: none` inside `@media (forced-colors)` - actively opts out of Windows High Contrast Mode |
97
-
98
- ### Severity changes
105
+ - **New rule reference pages:** RULES.md is now an index. Full references split into:
106
+ - [RULES-MARKUP.md](RULES-MARKUP.md): ESLint markup rules
107
+ - [RULES-CSS.md](RULES-CSS.md): Stylelint CSS rules
108
+ - [RULES-CONTENT.md](RULES-CONTENT.md): content rules with sources and methodology
109
+ - **New entry point alias:** `@a11yfred/neighbor/stylelint` added as an explicit stylelint alias alongside the default export.
110
+ - **Additional rules:** `no-labelledby-missing-target`, `no-dynamic-content-without-live`, `form-field-multiple-labels`, `no-empty-table-header` added to core markup rules (all run on React, Vue, and Angular).
99
111
 
100
- 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:
112
+ ### Changed
101
113
 
102
- `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`
114
+ - **Severity changes:** 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:
115
+ - `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`
116
+ - `no-tooltip-role-misuse` and `no-menu-role-on-nav` remain on as warns.
117
+ - **Extended rules:** `no-announce-in-render` now runs in Vue and Angular plugins (not just React). Safe contexts are tuned per framework: Vue recognizes `onMounted`, `watch`, `watchEffect`, `nextTick`; Angular recognizes `ngOnInit`, `ngAfterViewInit`, `ngOnChanges`, and class method event handlers.
103
118
 
104
- `no-tooltip-role-misuse` and `no-menu-role-on-nav` remain on as warns.
105
-
106
- ### Docs
119
+ ### Documentation
107
120
 
108
121
  - WCAG SC and HTML spec links added throughout README and RULES.md
109
122
  - CONTRIBUTING.md, PR template, and issue templates added
110
123
  - README table of contents added
124
+ - README now includes correct parser snippets for Vue and Angular, and separate setup sections for Remix 2 and Remix 3
111
125
  - @ulam described as a JavaScript framework (not React-based)
112
126
 
113
127
  ---
114
128
 
115
- ## 0.2.0 - 2026-05-12
116
-
117
- ### New rules
118
-
119
- | Rule | What it catches |
120
- | --- | --- |
121
- | `no-labelledby-missing-target` | `aria-labelledby`/`describedby`/`controls`/`owns`/`activedescendant` referencing an `id` that doesn't exist in the file |
122
- | `no-dynamic-content-without-live` | `dangerouslySetInnerHTML` / `v-html` / `[innerHTML]` on an element outside a live region |
123
- | `form-field-multiple-labels` | Multiple `<label for="…">` elements targeting the same input |
124
- | `no-empty-table-header` | `<th>` or `role="columnheader"/"rowheader"` with no accessible text |
125
-
126
- All four rules run on React, Vue, and Angular.
127
-
128
- ### Extended rules
129
-
130
- **`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.
131
-
132
- ### Setup improvements
133
-
134
- README now includes correct parser snippets for Vue and Angular, and separate setup sections for Remix 2 and Remix 3.
135
-
136
- ---
137
-
138
- ## 0.1.0 - 2026-04-30
129
+ ## 0.1.0 - 2026-04-30
139
130
 
140
131
  Initial release.
package/CONTRIBUTING.md CHANGED
@@ -1,23 +1,23 @@
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 - practitioners, AT users, spec readers, and people who have found a gap in existing tooling.
3
+ Neighbor is maintained by [@a11yfred](https://github.com/a11yfred). We welcome help from the accessibility community: experts, screen reader users, spec readers, and anyone who sees something missing in other tools.
4
4
 
5
5
  ## What belongs here
6
6
 
7
- A rule belongs in neighbor if it meets all three criteria:
7
+ A rule belongs in neighbor if it meets all three checks:
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. **Easy to find in code**: The linter can find the problem just by looking at the code, without needing a browser. Things like color contrast belong in `axe-core`.
10
+ 2. **Not in other tools**: Plugins like `jsx-a11y` or `vuejs-accessibility` do not already check it.
11
+ 3. **Expert-backed**: A rule must come from WCAG, ARIA specs, or accessibility experts.
12
12
 
13
- If you're unsure, open an issue before writing a rule. A brief description and a source is enough to start a conversation.
13
+ If you are not sure, open an issue before writing the rule. Just describe the idea and where it comes from to start a conversation.
14
14
 
15
- ## What doesn't belong here
15
+ ## What does not belong here
16
16
 
17
- - Rules that require runtime information (computed styles, DOM layout, AT output)
18
- - Rules already in jsx-a11y recommended - neighbor extends it, not replaces it
19
- - Opinionated style rules without a clear accessibility impact
20
- - Rules with very high false-positive rates on real codebases (see the rejected rules list in [RULES.md](RULES.md))
17
+ - Rules that need a browser to run (like checking CSS layout).
18
+ - Rules already in the standard accessibility linter for your framework (e.g., `jsx-a11y`, `vuejs-accessibility`, `@angular-eslint/template`, or `lit-a11y`). Neighbor works with them, it does not replace them.
19
+ - Code style rules that do not affect accessibility.
20
+ - Rules that give too many false warnings on real projects (see rejected rules in [RULES.md](RULES.md)).
21
21
 
22
22
  ## Setup
23
23
 
@@ -27,7 +27,7 @@ cd neighbor
27
27
  npm install
28
28
  ```
29
29
 
30
- No build step. Rules are plain ES modules - edit and run ESLint directly.
30
+ There is no build step. Rules are simple JavaScript files. You can edit them and run ESLint directly.
31
31
 
32
32
  ## How rules are structured
33
33
 
@@ -53,43 +53,43 @@ export function makeMyNewRule(h) {
53
53
  }
54
54
  ```
55
55
 
56
- The `h` adapter gives you a uniform interface across all three frameworks:
56
+ The `h` helper gives you a single way to check code across React, Vue, and Angular:
57
57
 
58
58
  | Helper | Returns |
59
- |---|---|
59
+ | --- | --- |
60
+ | `h.elementVisitor` | AST node type string for `create()` visitor key |
61
+ | `h.elementWithChildrenVisitor` | visitor key for rules that need child access |
62
+ | `h.getAncestors(node)` | iterable of ancestor element nodes, root-ward |
60
63
  | `h.getAttr(node, name)` | attribute node or `null` |
61
64
  | `h.getAttrStringValue(attr)` | string or `null` (null for dynamic expressions) |
65
+ | `h.getChildOpeningElements(node)` | iterable of direct child element nodes |
62
66
  | `h.getElementName(node)` | lowercase tag name, or `null` for custom components |
63
- | `h.hasAttr(node, name)` | boolean |
67
+ | `h.getInnerHtmlAttr(node)` | `dangerouslySetInnerHTML` / `v-html` / `[innerHTML]` node or `null` |
68
+ | `h.getParent(node)` | parent element node or `null` |
64
69
  | `h.getRoleValue(node)` | role string or `null` |
65
- | `h.hasAccessibleName(node)` | boolean - checks `aria-label` / `aria-labelledby` |
70
+ | `h.hasAccessibleName(node)` | boolean: checks `aria-label` / `aria-labelledby` |
71
+ | `h.hasAttr(node, name)` | boolean |
66
72
  | `h.isInteractiveElement(node)` | boolean |
67
- | `h.getParent(node)` | parent element node or `null` |
68
- | `h.getAncestors(node)` | iterable of ancestor element nodes, root-ward |
69
- | `h.getChildOpeningElements(node)` | iterable of direct child element nodes |
70
- | `h.getInnerHtmlAttr(node)` | `dangerouslySetInnerHTML` / `v-html` / `[innerHTML]` node or `null` |
71
- | `h.elementVisitor` | AST node type string for `create()` visitor key |
72
- | `h.elementWithChildrenVisitor` | visitor key for rules that need child access |
73
73
 
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).
74
+ **Angular warning:** `getParent()` and `getAncestors()` return `null` in Angular. The parser does not link child nodes to parent nodes. If your rule needs to look at parent nodes, it should fail quietly (skip the check, do not throw an error).
75
75
 
76
- After writing your factory:
76
+ After writing your rule function:
77
77
 
78
- 1. Add it to `RULE_FACTORIES` at the bottom of `lib/rules.js`
79
- 2. Add it to `buildRecommendedRules()` at the appropriate severity (`'error'` / `'warn'` / `'off'`)
80
- 3. If it's Vue/Angular-only (porting a jsx-a11y gap), add it to `buildPortabilityRules()` instead
78
+ 1. Add it to `RULE_FACTORIES` at the bottom of `lib/rules.js`.
79
+ 2. Add it to `buildRecommendedRules()` and set the severity (`'error'`, `'warn'`, or `'off'`).
80
+ 3. If the rule is only for Vue or Angular, add it to `buildPortabilityRules()` instead.
81
81
 
82
- Stylelint rules live in [`neighbor-stylelint.mjs`](neighbor-stylelint.mjs) and use the PostCSS AST directly. See the existing rules for the pattern.
82
+ Stylelint rules live in [`neighbor-stylelint.mjs`](neighbor-stylelint.mjs). They use PostCSS. Look at the existing rules to see how they work.
83
83
 
84
84
  ## Severity guidance
85
85
 
86
86
  | Severity | When to use |
87
- |---|---|
88
- | `error` | Unambiguous AT breakage - a phantom control, broken name computation, HTML spec violation. No legitimate override. |
89
- | `warn` | Strong guidance with a clear accessibility basis, but real codebases occasionally have justified exceptions. |
90
- | `off` | Real problem, but fires too often on legitimate patterns to be on by default. Make it available; let teams opt in. |
87
+ | --- | --- |
88
+ | `error` | This breaks screen readers or violates HTML rules. There is no good reason to do this. |
89
+ | `warn` | This is usually bad, but sometimes there is a good reason to do it. |
90
+ | `off` | This rule gives too many warnings on normal code to be turned on by default. Users can turn it on if they want. |
91
91
 
92
- When in doubt, start at `warn`. It's easier to promote a rule to `error` than to demote it after people have already configured it.
92
+ If you are not sure, start with `warn`. It is easier to change a `warn` to an `error` later.
93
93
 
94
94
  ## Commit style
95
95
 
@@ -99,17 +99,17 @@ fix: correct false positive in no-existing-rule
99
99
  docs: update RULES.md for no-my-new-rule
100
100
  ```
101
101
 
102
- No ticket numbers required.
102
+ You do not need to include issue ticket numbers in your commit messages.
103
103
 
104
104
  ## Opening a PR
105
105
 
106
- Use the PR template. The key things:
106
+ Please use the PR template. Here are the most important things:
107
107
 
108
- - **What problem does this flag?** Link a WCAG SC, ARIA spec section, or expert source.
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 - we'd rather move a rule to `off` than reject it.
111
- - **Does it degrade gracefully for Angular?** (Parent walking unavailable.)
108
+ - **What problem does this find?** Link to WCAG, ARIA specs, or an expert guide.
109
+ - **Why can axe-core not catch this?** If `axe-core` can catch it, the rule probably belongs there.
110
+ - **When will this give false warnings?** Be honest. We prefer to set a rule to `off` instead of rejecting your PR.
111
+ - **Does it fail quietly in Angular?** Remember that parent-walking does not work in Angular.
112
112
 
113
113
  ## Questions
114
114
 
115
- Open an issue or reach out in the [Web A11y Slack](https://web-a11y.slack.com). The `#tools` channel is a good place to discuss rule ideas before writing code.
115
+ Open an issue or talk to us in the [Web A11y Slack](https://web-a11y.slack.com). The `#tools` channel is a great place to talk about rule ideas before you start writing code.