next-a11y 0.1.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 +161 -0
- package/bin/cli.js +2 -0
- package/dist/chunk-PE4WYXR5.mjs +50 -0
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2183 -0
- package/dist/cli/index.mjs +2124 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +34 -0
- package/dist/index.mjs +6 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# next-a11y
|
|
2
|
+
|
|
3
|
+
**Finds accessibility violations in your Next.js source code. Writes the fix.**
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
jsx-a11y tells you the alt is missing.
|
|
7
|
+
axe tells you the button has no accessible name.
|
|
8
|
+
next-a11y writes the fix.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
95% of websites fail WCAG 2.2 AA. The same 6 error types. Every year. For 5 years straight. ([WebAIM Million 2025](https://webaim.org/projects/million/))
|
|
12
|
+
|
|
13
|
+
next-a11y fixes 4 of those 6 automatically — and catches the rest.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx next-a11y init # pick AI provider, generates config
|
|
19
|
+
npx next-a11y scan ./src # see what's broken
|
|
20
|
+
npx next-a11y scan ./src --fix # fix it
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Before
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
╭──────────────────────────────────────╮
|
|
27
|
+
│ Accessibility Score: 34 / 100 🔴 │
|
|
28
|
+
╰──────────────────────────────────────╯
|
|
29
|
+
|
|
30
|
+
🖼️ 12 images missing alt text -24 pts
|
|
31
|
+
🔘 4 icon buttons without aria-label -8 pts
|
|
32
|
+
🔗 2 icon links without aria-label -4 pts
|
|
33
|
+
📝 3 inputs without label -9 pts
|
|
34
|
+
🌐 1 missing lang on <html> -5 pts
|
|
35
|
+
...and 21 more issues
|
|
36
|
+
|
|
37
|
+
38 fixable · 4 warnings · Run --fix to apply
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### After
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
╭──────────────────────────────────────╮
|
|
44
|
+
│ Accessibility Score: 97 / 100 🟢 │
|
|
45
|
+
╰──────────────────────────────────────╯
|
|
46
|
+
|
|
47
|
+
✅ All auto-fixable issues resolved
|
|
48
|
+
|
|
49
|
+
0 fixable · 4 warnings · Score: 34 → 97 (+63 pts)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What it fixes
|
|
53
|
+
|
|
54
|
+
### AI-powered (vision + text models)
|
|
55
|
+
|
|
56
|
+
| Rule | What it does |
|
|
57
|
+
| -------------- | --------------------------------------------------------------- |
|
|
58
|
+
| `img-alt` | Sends image to vision model → generates WCAG-compliant alt text |
|
|
59
|
+
| `button-label` | Icon button → reads icon name + context → `aria-label` |
|
|
60
|
+
| `link-label` | Icon link → same approach |
|
|
61
|
+
| `input-label` | Unlabeled input → generates `<label>` or `aria-label` |
|
|
62
|
+
|
|
63
|
+
### Deterministic (zero AI cost, no API key)
|
|
64
|
+
|
|
65
|
+
| Rule | What it does |
|
|
66
|
+
| ---------------------- | ----------------------------------------------------- |
|
|
67
|
+
| `html-lang` | Reads `next.config.js` locale → inserts `lang` |
|
|
68
|
+
| `emoji-alt` | `🔥` → `<span role="img" aria-label="fire">🔥</span>` |
|
|
69
|
+
| `no-positive-tabindex` | `tabIndex={5}` → `tabIndex={0}` |
|
|
70
|
+
| `button-type` | `<button>` → `<button type="button">` |
|
|
71
|
+
| `link-noopener` | `target="_blank"` → adds `rel="noopener noreferrer"` |
|
|
72
|
+
|
|
73
|
+
### Next.js-specific
|
|
74
|
+
|
|
75
|
+
| Rule | What it does |
|
|
76
|
+
| ----------------------- | ------------------------------------------------------------------------ |
|
|
77
|
+
| `next-metadata-title` | Warns if `layout.tsx` / `page.tsx` has no title (breaks route announcer) |
|
|
78
|
+
| `next-image-sizes` | Warns if `<Image fill>` is missing `sizes` |
|
|
79
|
+
| `next-link-no-nested-a` | Removes `<Link><a>` double anchor (Next 12→13 migration artifact) |
|
|
80
|
+
| `next-skip-nav` | Warns if root layout has no skip navigation link |
|
|
81
|
+
|
|
82
|
+
### Detection-only (reports, human decides)
|
|
83
|
+
|
|
84
|
+
| Rule | What it does |
|
|
85
|
+
| -------------------- | -------------------------------------------------------- |
|
|
86
|
+
| `heading-order` | Flags `h1` → `h3` skips |
|
|
87
|
+
| `no-div-interactive` | Flags `<div onClick>` without `role` or keyboard handler |
|
|
88
|
+
|
|
89
|
+
## No AI? No problem.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx next-a11y scan ./src --fix --no-ai
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
9 deterministic rules work without any API key or AI setup.
|
|
96
|
+
|
|
97
|
+
## AI providers
|
|
98
|
+
|
|
99
|
+
Works with any major provider through [Vercel AI SDK](https://sdk.vercel.ai/):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install @ai-sdk/google # free tier: 1500 req/day
|
|
103
|
+
# or
|
|
104
|
+
npm install @ai-sdk/openai # gpt-4.1-nano ~$0.001/fix
|
|
105
|
+
# or
|
|
106
|
+
npm install @ai-sdk/anthropic # claude-haiku ~$0.001/fix
|
|
107
|
+
# or
|
|
108
|
+
npm install ollama-ai-provider # local, $0
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Config
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// a11y.config.ts
|
|
115
|
+
import { defineConfig } from "next-a11y";
|
|
116
|
+
|
|
117
|
+
export default defineConfig({
|
|
118
|
+
provider: "google",
|
|
119
|
+
model: "gemini-2.0-flash-lite",
|
|
120
|
+
locale: "en",
|
|
121
|
+
scanner: {
|
|
122
|
+
include: ["src/**/*.{tsx,jsx}"],
|
|
123
|
+
exclude: ["**/*.test.*", "**/*.stories.*"],
|
|
124
|
+
},
|
|
125
|
+
rules: {
|
|
126
|
+
"img-alt": "fix", // 'fix' | 'warn' | 'off'
|
|
127
|
+
"button-label": "fix",
|
|
128
|
+
"emoji-alt": "fix",
|
|
129
|
+
"heading-order": "warn",
|
|
130
|
+
// ...all 15 rules configurable
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## CI
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
# .github/workflows/a11y.yml
|
|
139
|
+
- name: Accessibility check
|
|
140
|
+
run: npx next-a11y scan ./src --min-score 80
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Exits with code 1 below threshold. Block PRs that regress accessibility.
|
|
144
|
+
|
|
145
|
+
## Try it
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
git clone https://github.com/MaciejWiatr/next-a11y
|
|
149
|
+
cd next-a11y/examples/broken-site
|
|
150
|
+
npx next-a11y scan . --fix
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`broken-site` is an intentionally inaccessible Next.js app that triggers all 15 rules.
|
|
154
|
+
|
|
155
|
+
## How it works
|
|
156
|
+
|
|
157
|
+
Static analysis codemod. Parses your source with [ts-morph](https://github.com/dsherret/ts-morph), runs 15 rules against the AST, generates fixes (AI or pattern-based), writes them back to your files. No browser. No runtime. Ships zero code to production.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/config/schema.ts
|
|
2
|
+
var PROVIDER_DEFAULTS = {
|
|
3
|
+
openai: "gpt-4.1-nano",
|
|
4
|
+
anthropic: "claude-haiku-4-5-20251001",
|
|
5
|
+
google: "gemini-2.0-flash-lite",
|
|
6
|
+
ollama: "llava"
|
|
7
|
+
};
|
|
8
|
+
var PROVIDER_ENV = {
|
|
9
|
+
openai: "OPENAI_API_KEY",
|
|
10
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
11
|
+
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
12
|
+
ollama: null
|
|
13
|
+
};
|
|
14
|
+
var DEFAULT_RULES = {
|
|
15
|
+
"img-alt": "fix",
|
|
16
|
+
"button-label": "fix",
|
|
17
|
+
"link-label": "fix",
|
|
18
|
+
"input-label": "fix",
|
|
19
|
+
"html-lang": "fix",
|
|
20
|
+
"emoji-alt": "fix",
|
|
21
|
+
"no-positive-tabindex": "fix",
|
|
22
|
+
"button-type": "fix",
|
|
23
|
+
"link-noopener": "fix",
|
|
24
|
+
"next-metadata-title": "warn",
|
|
25
|
+
"next-image-sizes": "warn",
|
|
26
|
+
"next-link-no-nested-a": "fix",
|
|
27
|
+
"next-skip-nav": "warn",
|
|
28
|
+
"heading-order": "warn",
|
|
29
|
+
"no-div-interactive": "warn"
|
|
30
|
+
};
|
|
31
|
+
var DEFAULT_CONFIG = {
|
|
32
|
+
locale: "en",
|
|
33
|
+
cache: ".a11y-cache",
|
|
34
|
+
scanner: {
|
|
35
|
+
include: ["**/*.{tsx,jsx}"],
|
|
36
|
+
exclude: ["**/*.test.*", "**/*.spec.*", "**/*.stories.*", "**/node_modules/**"]
|
|
37
|
+
},
|
|
38
|
+
rules: DEFAULT_RULES
|
|
39
|
+
};
|
|
40
|
+
function defineConfig(config) {
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
PROVIDER_DEFAULTS,
|
|
46
|
+
PROVIDER_ENV,
|
|
47
|
+
DEFAULT_RULES,
|
|
48
|
+
DEFAULT_CONFIG,
|
|
49
|
+
defineConfig
|
|
50
|
+
};
|