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 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,2 @@
1
+ #!/usr/bin/env node
2
+ require("../dist/cli/index.js");
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }