developer-ai 1.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/README.md +241 -0
- package/bin/developer-ai.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +219 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +82 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +115 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +29 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/agent.d.ts +38 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +155 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/system-prompt.d.ts +6 -0
- package/dist/core/system-prompt.d.ts.map +1 -0
- package/dist/core/system-prompt.js +44 -0
- package/dist/core/system-prompt.js.map +1 -0
- package/dist/core/types.d.ts +42 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/client.d.ts +13 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +202 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/providers/ollama.d.ts +13 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +60 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +9 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +40 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/skills/loader.d.ts +25 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +93 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/tests/tools.test.d.ts +2 -0
- package/dist/tests/tools.test.d.ts.map +1 -0
- package/dist/tests/tools.test.js +170 -0
- package/dist/tests/tools.test.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +19 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-files.d.ts +3 -0
- package/dist/tools/list-files.d.ts.map +1 -0
- package/dist/tools/list-files.js +60 -0
- package/dist/tools/list-files.js.map +1 -0
- package/dist/tools/read-file.d.ts +3 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +46 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/registry.d.ts +24 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +37 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/run-command.d.ts +3 -0
- package/dist/tools/run-command.d.ts.map +1 -0
- package/dist/tools/run-command.js +114 -0
- package/dist/tools/run-command.js.map +1 -0
- package/dist/tools/search-text.d.ts +3 -0
- package/dist/tools/search-text.d.ts.map +1 -0
- package/dist/tools/search-text.js +103 -0
- package/dist/tools/search-text.js.map +1 -0
- package/dist/tools/utils.d.ts +6 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +14 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/tools/web-search.d.ts +3 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +80 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/write-file.d.ts +3 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +66 -0
- package/dist/tools/write-file.js.map +1 -0
- package/package.json +54 -0
- package/skills/accessibility/SKILL.md +496 -0
- package/skills/api-design/SKILL.md +419 -0
- package/skills/code-review/SKILL.md +267 -0
- package/skills/debugging/SKILL.md +332 -0
- package/skills/documentation/SKILL.md +496 -0
- package/skills/error-handling/SKILL.md +504 -0
- package/skills/git-workflow/SKILL.md +448 -0
- package/skills/human-like-coding/SKILL.md +400 -0
- package/skills/performance-optimization/SKILL.md +412 -0
- package/skills/prompt-engineering/SKILL.md +362 -0
- package/skills/refactoring/SKILL.md +457 -0
- package/skills/security-audit/SKILL.md +453 -0
- package/skills/testing-strategy/SKILL.md +501 -0
- package/skills/webapp-testing/SKILL.md +309 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: accessibility
|
|
3
|
+
description: Guide for building accessible web applications. Use when implementing UI components, reviewing for accessibility compliance, or fixing accessibility issues. Covers WCAG guidelines, ARIA attributes, keyboard navigation, and testing tools.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Accessibility Skill
|
|
7
|
+
|
|
8
|
+
This skill provides guidance for building inclusive web applications that work for everyone.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Accessibility (a11y) ensures your application is usable by people with disabilities. This skill covers WCAG guidelines, semantic HTML, ARIA, and testing approaches.
|
|
13
|
+
|
|
14
|
+
## WCAG Principles (POUR)
|
|
15
|
+
|
|
16
|
+
| Principle | Description |
|
|
17
|
+
|-----------|-------------|
|
|
18
|
+
| **Perceivable** | Information must be presentable in ways users can perceive |
|
|
19
|
+
| **Operable** | UI components must be operable by all users |
|
|
20
|
+
| **Understandable** | Information and UI must be understandable |
|
|
21
|
+
| **Robust** | Content must work with current and future technologies |
|
|
22
|
+
|
|
23
|
+
## Semantic HTML
|
|
24
|
+
|
|
25
|
+
### Use Appropriate Elements
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<!-- BAD: Div soup -->
|
|
29
|
+
<div class="header">
|
|
30
|
+
<div class="nav">
|
|
31
|
+
<div class="link" onclick="navigate()">Home</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="main-content">
|
|
35
|
+
<div class="article">
|
|
36
|
+
<div class="title">Article Title</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="footer">Copyright 2024</div>
|
|
40
|
+
|
|
41
|
+
<!-- GOOD: Semantic elements -->
|
|
42
|
+
<header>
|
|
43
|
+
<nav>
|
|
44
|
+
<a href="/">Home</a>
|
|
45
|
+
</nav>
|
|
46
|
+
</header>
|
|
47
|
+
<main>
|
|
48
|
+
<article>
|
|
49
|
+
<h1>Article Title</h1>
|
|
50
|
+
</article>
|
|
51
|
+
</main>
|
|
52
|
+
<footer>Copyright 2024</footer>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Heading Hierarchy
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<!-- BAD: Skipped levels, wrong order -->
|
|
59
|
+
<h1>Page Title</h1>
|
|
60
|
+
<h3>Section</h3>
|
|
61
|
+
<h2>Subsection</h2>
|
|
62
|
+
|
|
63
|
+
<!-- GOOD: Logical hierarchy -->
|
|
64
|
+
<h1>Page Title</h1>
|
|
65
|
+
<h2>Section</h2>
|
|
66
|
+
<h3>Subsection</h3>
|
|
67
|
+
<h3>Another Subsection</h3>
|
|
68
|
+
<h2>Next Section</h2>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Buttons vs Links
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<!-- Links: Navigation -->
|
|
75
|
+
<a href="/products">View Products</a>
|
|
76
|
+
|
|
77
|
+
<!-- Buttons: Actions -->
|
|
78
|
+
<button type="button" onclick="openModal()">Open Settings</button>
|
|
79
|
+
|
|
80
|
+
<!-- BAD: Div as button -->
|
|
81
|
+
<div class="button" onclick="submit()">Submit</div>
|
|
82
|
+
|
|
83
|
+
<!-- BAD: Link as button -->
|
|
84
|
+
<a href="#" onclick="submit()">Submit</a>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## ARIA Attributes
|
|
88
|
+
|
|
89
|
+
### When to Use ARIA
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
First rule of ARIA: Don't use ARIA if native HTML works.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Common ARIA Attributes
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<!-- Labels -->
|
|
99
|
+
<button aria-label="Close modal">×</button>
|
|
100
|
+
<input type="search" aria-label="Search products">
|
|
101
|
+
|
|
102
|
+
<!-- Descriptions -->
|
|
103
|
+
<input
|
|
104
|
+
type="password"
|
|
105
|
+
aria-describedby="password-help"
|
|
106
|
+
>
|
|
107
|
+
<p id="password-help">Must be at least 8 characters</p>
|
|
108
|
+
|
|
109
|
+
<!-- States -->
|
|
110
|
+
<button aria-pressed="true">Bold</button>
|
|
111
|
+
<button aria-expanded="false" aria-controls="menu">Menu</button>
|
|
112
|
+
<div id="menu" hidden>...</div>
|
|
113
|
+
|
|
114
|
+
<!-- Live Regions -->
|
|
115
|
+
<div aria-live="polite" aria-atomic="true">
|
|
116
|
+
Cart updated: 3 items
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Roles -->
|
|
120
|
+
<div role="alert">Error: Invalid email</div>
|
|
121
|
+
<nav role="navigation" aria-label="Main">...</nav>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### ARIA for Custom Components
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<!-- Custom checkbox -->
|
|
128
|
+
<div
|
|
129
|
+
role="checkbox"
|
|
130
|
+
aria-checked="false"
|
|
131
|
+
tabindex="0"
|
|
132
|
+
onkeydown="handleKeydown(event)"
|
|
133
|
+
onclick="toggle()"
|
|
134
|
+
>
|
|
135
|
+
Accept terms
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Custom dropdown -->
|
|
139
|
+
<div class="dropdown">
|
|
140
|
+
<button
|
|
141
|
+
aria-haspopup="listbox"
|
|
142
|
+
aria-expanded="false"
|
|
143
|
+
aria-controls="options"
|
|
144
|
+
>
|
|
145
|
+
Select option
|
|
146
|
+
</button>
|
|
147
|
+
<ul
|
|
148
|
+
id="options"
|
|
149
|
+
role="listbox"
|
|
150
|
+
aria-label="Options"
|
|
151
|
+
hidden
|
|
152
|
+
>
|
|
153
|
+
<li role="option" aria-selected="false">Option 1</li>
|
|
154
|
+
<li role="option" aria-selected="false">Option 2</li>
|
|
155
|
+
</ul>
|
|
156
|
+
</div>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Keyboard Navigation
|
|
160
|
+
|
|
161
|
+
### Focus Management
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// Focus first element in modal when opened
|
|
165
|
+
function openModal() {
|
|
166
|
+
modal.hidden = false;
|
|
167
|
+
modal.querySelector('[autofocus], button, [href], input').focus();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Return focus when modal closes
|
|
171
|
+
let previousFocus;
|
|
172
|
+
|
|
173
|
+
function openModal() {
|
|
174
|
+
previousFocus = document.activeElement;
|
|
175
|
+
modal.hidden = false;
|
|
176
|
+
// Focus first element
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function closeModal() {
|
|
180
|
+
modal.hidden = true;
|
|
181
|
+
previousFocus.focus();
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Focus Trap
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
function trapFocus(element) {
|
|
189
|
+
const focusableElements = element.querySelectorAll(
|
|
190
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
191
|
+
);
|
|
192
|
+
const firstElement = focusableElements[0];
|
|
193
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
194
|
+
|
|
195
|
+
element.addEventListener('keydown', (e) => {
|
|
196
|
+
if (e.key !== 'Tab') return;
|
|
197
|
+
|
|
198
|
+
if (e.shiftKey) {
|
|
199
|
+
if (document.activeElement === firstElement) {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
lastElement.focus();
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
if (document.activeElement === lastElement) {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
firstElement.focus();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Skip Links
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<body>
|
|
217
|
+
<a href="#main-content" class="skip-link">
|
|
218
|
+
Skip to main content
|
|
219
|
+
</a>
|
|
220
|
+
|
|
221
|
+
<header>...</header>
|
|
222
|
+
<nav>...</nav>
|
|
223
|
+
|
|
224
|
+
<main id="main-content" tabindex="-1">
|
|
225
|
+
<!-- Main content -->
|
|
226
|
+
</main>
|
|
227
|
+
</body>
|
|
228
|
+
|
|
229
|
+
<style>
|
|
230
|
+
.skip-link {
|
|
231
|
+
position: absolute;
|
|
232
|
+
top: -40px;
|
|
233
|
+
left: 0;
|
|
234
|
+
padding: 8px;
|
|
235
|
+
background: white;
|
|
236
|
+
z-index: 100;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.skip-link:focus {
|
|
240
|
+
top: 0;
|
|
241
|
+
}
|
|
242
|
+
</style>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Forms
|
|
246
|
+
|
|
247
|
+
### Labels
|
|
248
|
+
|
|
249
|
+
```html
|
|
250
|
+
<!-- Explicit label -->
|
|
251
|
+
<label for="email">Email</label>
|
|
252
|
+
<input type="email" id="email" name="email">
|
|
253
|
+
|
|
254
|
+
<!-- Implicit label -->
|
|
255
|
+
<label>
|
|
256
|
+
Email
|
|
257
|
+
<input type="email" name="email">
|
|
258
|
+
</label>
|
|
259
|
+
|
|
260
|
+
<!-- With required indicator -->
|
|
261
|
+
<label for="email">
|
|
262
|
+
Email <span aria-hidden="true">*</span>
|
|
263
|
+
</label>
|
|
264
|
+
<input
|
|
265
|
+
type="email"
|
|
266
|
+
id="email"
|
|
267
|
+
required
|
|
268
|
+
aria-required="true"
|
|
269
|
+
>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Error Messages
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<label for="email">Email</label>
|
|
276
|
+
<input
|
|
277
|
+
type="email"
|
|
278
|
+
id="email"
|
|
279
|
+
aria-invalid="true"
|
|
280
|
+
aria-describedby="email-error"
|
|
281
|
+
>
|
|
282
|
+
<p id="email-error" role="alert">
|
|
283
|
+
Please enter a valid email address
|
|
284
|
+
</p>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Form Groups
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<fieldset>
|
|
291
|
+
<legend>Shipping Address</legend>
|
|
292
|
+
|
|
293
|
+
<label for="street">Street</label>
|
|
294
|
+
<input type="text" id="street" name="street">
|
|
295
|
+
|
|
296
|
+
<label for="city">City</label>
|
|
297
|
+
<input type="text" id="city" name="city">
|
|
298
|
+
</fieldset>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Images
|
|
302
|
+
|
|
303
|
+
### Alt Text
|
|
304
|
+
|
|
305
|
+
```html
|
|
306
|
+
<!-- Informative image -->
|
|
307
|
+
<img src="chart.png" alt="Sales increased 25% in Q4 2024">
|
|
308
|
+
|
|
309
|
+
<!-- Decorative image -->
|
|
310
|
+
<img src="decoration.png" alt="" role="presentation">
|
|
311
|
+
|
|
312
|
+
<!-- Complex image -->
|
|
313
|
+
<figure>
|
|
314
|
+
<img src="chart.png" alt="Bar chart showing quarterly sales">
|
|
315
|
+
<figcaption>
|
|
316
|
+
Q1: $100k, Q2: $150k, Q3: $200k, Q4: $250k
|
|
317
|
+
</figcaption>
|
|
318
|
+
</figure>
|
|
319
|
+
|
|
320
|
+
<!-- Icon buttons -->
|
|
321
|
+
<button aria-label="Delete item">
|
|
322
|
+
<img src="trash.svg" alt="">
|
|
323
|
+
</button>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Color & Contrast
|
|
327
|
+
|
|
328
|
+
### Contrast Requirements
|
|
329
|
+
|
|
330
|
+
| Element | WCAG AA | WCAG AAA |
|
|
331
|
+
|---------|---------|----------|
|
|
332
|
+
| Normal text | 4.5:1 | 7:1 |
|
|
333
|
+
| Large text (18px+) | 3:1 | 4.5:1 |
|
|
334
|
+
| UI components | 3:1 | - |
|
|
335
|
+
|
|
336
|
+
### Don't Rely on Color Alone
|
|
337
|
+
|
|
338
|
+
```html
|
|
339
|
+
<!-- BAD: Color only -->
|
|
340
|
+
<span style="color: red">Error</span>
|
|
341
|
+
<span style="color: green">Success</span>
|
|
342
|
+
|
|
343
|
+
<!-- GOOD: Color + text/icon -->
|
|
344
|
+
<span style="color: red">❌ Error: Invalid email</span>
|
|
345
|
+
<span style="color: green">✓ Success: Account created</span>
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## React Accessibility
|
|
349
|
+
|
|
350
|
+
### Accessible Components
|
|
351
|
+
|
|
352
|
+
```jsx
|
|
353
|
+
// Accessible Button
|
|
354
|
+
function IconButton({ icon, label, onClick }) {
|
|
355
|
+
return (
|
|
356
|
+
<button onClick={onClick} aria-label={label}>
|
|
357
|
+
{icon}
|
|
358
|
+
</button>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Accessible Modal
|
|
363
|
+
function Modal({ isOpen, onClose, title, children }) {
|
|
364
|
+
const modalRef = useRef();
|
|
365
|
+
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
if (isOpen) {
|
|
368
|
+
modalRef.current.focus();
|
|
369
|
+
}
|
|
370
|
+
}, [isOpen]);
|
|
371
|
+
|
|
372
|
+
if (!isOpen) return null;
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<div
|
|
376
|
+
role="dialog"
|
|
377
|
+
aria-modal="true"
|
|
378
|
+
aria-labelledby="modal-title"
|
|
379
|
+
ref={modalRef}
|
|
380
|
+
tabIndex={-1}
|
|
381
|
+
>
|
|
382
|
+
<h2 id="modal-title">{title}</h2>
|
|
383
|
+
{children}
|
|
384
|
+
<button onClick={onClose}>Close</button>
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Managing Focus
|
|
391
|
+
|
|
392
|
+
```jsx
|
|
393
|
+
function SearchForm() {
|
|
394
|
+
const inputRef = useRef();
|
|
395
|
+
const [results, setResults] = useState([]);
|
|
396
|
+
|
|
397
|
+
const handleSubmit = async (e) => {
|
|
398
|
+
e.preventDefault();
|
|
399
|
+
const data = await search(inputRef.current.value);
|
|
400
|
+
setResults(data);
|
|
401
|
+
// Announce results
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<form onSubmit={handleSubmit}>
|
|
406
|
+
<label htmlFor="search">Search</label>
|
|
407
|
+
<input
|
|
408
|
+
ref={inputRef}
|
|
409
|
+
id="search"
|
|
410
|
+
type="search"
|
|
411
|
+
aria-describedby="search-hint"
|
|
412
|
+
/>
|
|
413
|
+
<p id="search-hint">Press Enter to search</p>
|
|
414
|
+
|
|
415
|
+
<div role="region" aria-live="polite">
|
|
416
|
+
{results.length} results found
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<ul>
|
|
420
|
+
{results.map(item => (
|
|
421
|
+
<li key={item.id}>{item.name}</li>
|
|
422
|
+
))}
|
|
423
|
+
</ul>
|
|
424
|
+
</form>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## Testing Accessibility
|
|
430
|
+
|
|
431
|
+
### Automated Testing
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
// jest-axe
|
|
435
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
436
|
+
|
|
437
|
+
expect.extend(toHaveNoViolations);
|
|
438
|
+
|
|
439
|
+
it('has no accessibility violations', async () => {
|
|
440
|
+
const { container } = render(<MyComponent />);
|
|
441
|
+
const results = await axe(container);
|
|
442
|
+
expect(results).toHaveNoViolations();
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Manual Testing Checklist
|
|
447
|
+
|
|
448
|
+
- [ ] Navigate with keyboard only (Tab, Enter, Space, Arrows)
|
|
449
|
+
- [ ] Test with screen reader (VoiceOver, NVDA)
|
|
450
|
+
- [ ] Zoom to 200% - content still usable
|
|
451
|
+
- [ ] Check color contrast with tools
|
|
452
|
+
- [ ] Disable CSS - content still readable
|
|
453
|
+
- [ ] Test with reduced motion preference
|
|
454
|
+
|
|
455
|
+
### Browser Extensions
|
|
456
|
+
|
|
457
|
+
- **axe DevTools** - Automated a11y testing
|
|
458
|
+
- **WAVE** - Visual accessibility feedback
|
|
459
|
+
- **Lighthouse** - Built into Chrome DevTools
|
|
460
|
+
|
|
461
|
+
## Accessibility Checklist
|
|
462
|
+
|
|
463
|
+
### Structure
|
|
464
|
+
- [ ] Semantic HTML used
|
|
465
|
+
- [ ] Heading hierarchy correct
|
|
466
|
+
- [ ] Landmarks defined (header, nav, main, footer)
|
|
467
|
+
- [ ] Skip links provided
|
|
468
|
+
|
|
469
|
+
### Forms
|
|
470
|
+
- [ ] All inputs have labels
|
|
471
|
+
- [ ] Required fields marked
|
|
472
|
+
- [ ] Error messages associated
|
|
473
|
+
- [ ] Form groups used for related fields
|
|
474
|
+
|
|
475
|
+
### Images & Media
|
|
476
|
+
- [ ] Images have alt text
|
|
477
|
+
- [ ] Decorative images have empty alt
|
|
478
|
+
- [ ] Videos have captions
|
|
479
|
+
- [ ] Audio has transcripts
|
|
480
|
+
|
|
481
|
+
### Interaction
|
|
482
|
+
- [ ] All interactive elements keyboard accessible
|
|
483
|
+
- [ ] Focus visible and logical
|
|
484
|
+
- [ ] Focus trapped in modals
|
|
485
|
+
- [ ] Touch targets at least 44x44px
|
|
486
|
+
|
|
487
|
+
### Content
|
|
488
|
+
- [ ] Color contrast meets WCAG AA
|
|
489
|
+
- [ ] Information not conveyed by color alone
|
|
490
|
+
- [ ] Text resizable to 200%
|
|
491
|
+
- [ ] Language attribute set
|
|
492
|
+
|
|
493
|
+
### ARIA
|
|
494
|
+
- [ ] ARIA labels where needed
|
|
495
|
+
- [ ] Live regions for dynamic content
|
|
496
|
+
- [ ] States communicated (expanded, selected, etc.)
|