eslint-plugin-tailwind-a11y 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,15 +1,8 @@
1
1
  # eslint-plugin-tailwind-a11y
2
2
 
3
- ESLint rules that catch [WCAG](https://www.w3.org/WAI/WCAG21/quickref/) accessibility
4
- violations in Tailwind CSS class combinations color contrast, touch target size, and
5
- focus indicator removal as part of your normal `eslint` run, instead of a separate CLI
6
- you have to remember to invoke.
7
-
8
- This plugin is a thin ESLint adapter over the [`tailwind-a11y`](https://github.com/chamroro/tailwind-a11y/tree/main/packages/tailwind-a11y)
9
- engine, which does the actual work: resolving Tailwind utility classes back into real
10
- computed values (colors, sizes) via AST analysis. Every rule here calls straight into
11
- that engine and reports its violations — there's no separate implementation to drift out
12
- of sync, and no wording that differs between `npx tailwind-a11y` and `eslint .`.
3
+ ESLint rules for [`tailwind-a11y`](https://github.com/chamroro/tailwind-a11y/tree/main/packages/tailwind-a11y)
4
+ inline WCAG diagnostics during normal linting, powered by the same engine as the CLI
5
+ and VS Code extension. No detection logic is duplicated, so results never disagree.
13
6
 
14
7
  ## Install
15
8
 
@@ -25,17 +18,12 @@ Requires ESLint 9 or 10 (flat config).
25
18
  // eslint.config.js
26
19
  import tailwindA11y from "eslint-plugin-tailwind-a11y";
27
20
 
28
- export default [
29
- ...tailwindA11y.configs.recommended,
30
- ];
21
+ export default [...tailwindA11y.configs.recommended];
31
22
  ```
32
23
 
33
- `configs.recommended` scopes itself to `**/*.jsx`/`**/*.tsx` and enables all three rules
34
- as errors. To register rules manually instead:
24
+ Or register rules individually:
35
25
 
36
26
  ```js
37
- import tailwindA11y from "eslint-plugin-tailwind-a11y";
38
-
39
27
  export default [
40
28
  {
41
29
  files: ["**/*.jsx", "**/*.tsx"],
@@ -51,46 +39,20 @@ export default [
51
39
 
52
40
  ## Rules
53
41
 
54
- | Rule | WCAG | Catches |
42
+ | Rule | WCAG | Detects |
55
43
  |---|---|---|
56
- | `tailwind-a11y/contrast` | 1.4.3 (AA) | `text-*`/`bg-*` pairs below the 4.5:1 contrast ratio, same-element or direct-parent |
57
- | `tailwind-a11y/touch-target` | 2.5.8 (AA) | Interactive elements sized under 24×24px via `w-*`/`h-*` |
58
- | `tailwind-a11y/focus-indicator` | 2.4.7 (AA) | `focus:outline-none` with no visible replacement style |
59
-
60
- Each rule's exact scope and known limitations are documented in the
61
- [engine's README](https://github.com/chamroro/tailwind-a11y/tree/main/packages/tailwind-a11y#what-it-deliberately-doesnt-catch-v1-scope) —
62
- this plugin doesn't change what's checked, only how it's surfaced.
63
-
64
- ## `.tsx` files and parsers
44
+ | `contrast` | 1.4.3 (AA) | Low-contrast `text-*`/`bg-*` pairs suggests the nearest passing shade |
45
+ | `touch-target` | 2.5.8 (AA) | Interactive elements under 24×24px |
46
+ | `focus-indicator` | 2.4.7 (AA) | `focus:outline-none` with no visible replacement |
65
47
 
66
- The underlying engine parses each file's source independently with Babel (`jsx` +
67
- `typescript` plugins), so `.tsx` syntax is understood regardless of what parser your
68
- ESLint config uses. But ESLint itself still needs to parse the file *first* to run any
69
- rule at all — if your config doesn't already set a TypeScript-aware
70
- `languageOptions.parser` (e.g. `typescript-eslint`) for `.tsx` files, ESLint will fail to
71
- parse them before this plugin ever runs. If you already lint `.tsx` files today, you have
72
- this covered; nothing extra to add for this plugin specifically.
48
+ Exact scope and known limitations: [engine README](https://github.com/chamroro/tailwind-a11y/tree/main/packages/tailwind-a11y#scope).
73
49
 
74
- ## Relationship to the `tailwind-a11y` CLI
75
-
76
- Same checks, same engine, two ways to run them: `eslint-plugin-tailwind-a11y` for
77
- inline, per-file feedback during normal linting; [`tailwind-a11y`](https://github.com/chamroro/tailwind-a11y/tree/main/packages/tailwind-a11y)
78
- (the CLI) for a one-shot scan, e.g. in a CI step that doesn't otherwise run ESLint. Use
79
- either, or both — they'll never disagree, since the plugin doesn't reimplement anything.
80
-
81
- ## Development
82
-
83
- This package lives in the [`tailwind-a11y` monorepo](https://github.com/chamroro/tailwind-a11y)
84
- (npm workspaces) alongside the engine it depends on:
85
-
86
- ```bash
87
- npm install # from the monorepo root
88
- npm run build --workspaces # or: npm run build -w tailwind-a11y -w eslint-plugin-tailwind-a11y
89
- npm test --workspaces --if-present
90
- ```
50
+ ## Notes
91
51
 
92
- After changing the engine (`packages/tailwind-a11y`), rebuild it before running this
93
- package's tests the workspace dependency resolves through its `dist/`, not its `src/`.
52
+ `.tsx` files need a TypeScript-aware `languageOptions.parser` (e.g. `typescript-eslint`)
53
+ in your own config for ESLint to parse them at all. This plugin's own analysis
54
+ understands TypeScript syntax regardless — but ESLint has to parse the file successfully
55
+ before any rule, including this one, ever runs.
94
56
 
95
57
  ## License
96
58
 
@@ -10,6 +10,11 @@ const rule = {
10
10
  schema: [],
11
11
  messages: {
12
12
  contrast: "{{textClass}} on {{bgClass}} — ratio {{ratio}}, needs {{required}} ({{level}})",
13
+ // Separate messageId rather than an optional {{suggestion}} placeholder
14
+ // in the base template — ESLint's interpolate() renders literal
15
+ // "undefined" if the data key is present-but-undefined, and RuleTester
16
+ // hard-fails on an unsubstituted {{placeholder}} if the key is omitted.
17
+ contrastWithSuggestion: "{{textClass}} on {{bgClass}} — ratio {{ratio}}, needs {{required}} ({{level}}); try {{suggestion}} ({{suggestedRatio}})",
13
18
  },
14
19
  },
15
20
  create(context) {
@@ -20,16 +25,21 @@ const rule = {
20
25
  Program() {
21
26
  const violations = checkContrast(extractChecks(context.sourceCode.getText(), context.filename));
22
27
  for (const v of violations) {
28
+ const data = {
29
+ textClass: v.textClass,
30
+ bgClass: v.bgClass,
31
+ ratio: v.ratio.toFixed(2),
32
+ required: v.required,
33
+ level: v.level,
34
+ };
35
+ if (v.suggestion) {
36
+ data.suggestion = v.suggestion;
37
+ data.suggestedRatio = v.suggestedRatio.toFixed(2);
38
+ }
23
39
  context.report({
24
40
  loc: { line: v.line, column: 0 },
25
- messageId: "contrast",
26
- data: {
27
- textClass: v.textClass,
28
- bgClass: v.bgClass,
29
- ratio: v.ratio.toFixed(2),
30
- required: v.required,
31
- level: v.level,
32
- },
41
+ messageId: v.suggestion ? "contrastWithSuggestion" : "contrast",
42
+ data,
33
43
  });
34
44
  }
35
45
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-tailwind-a11y",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "ESLint rules that catch WCAG accessibility violations — color contrast, touch target size, and focus indicator removal — in Tailwind CSS class combinations.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,7 +12,9 @@
12
12
  },
13
13
  "./package.json": "./package.json"
14
14
  },
15
- "files": ["dist"],
15
+ "files": [
16
+ "dist"
17
+ ],
16
18
  "engines": {
17
19
  "node": "^20.19.0 || ^22.13.0 || >=24"
18
20
  },
@@ -47,7 +49,7 @@
47
49
  },
48
50
  "homepage": "https://github.com/chamroro/tailwind-a11y/tree/main/packages/eslint-plugin-tailwind-a11y#readme",
49
51
  "dependencies": {
50
- "tailwind-a11y": "^0.1.0"
52
+ "tailwind-a11y": "^0.2.0"
51
53
  },
52
54
  "peerDependencies": {
53
55
  "eslint": "^9.0.0 || ^10.0.0"