@champpaba/claude-agent-kit 3.0.2 → 3.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/.claude/CHANGELOG.md +707 -0
- package/.claude/CLAUDE.md +128 -613
- package/.claude/agents/_shared/pre-work-checklist.md +108 -7
- package/.claude/commands/cdev.md +36 -0
- package/.claude/commands/csetup.md +292 -1791
- package/.claude/commands/cview.md +364 -364
- package/.claude/contexts/design/accessibility.md +611 -611
- package/.claude/contexts/design/layout.md +400 -400
- package/.claude/contexts/design/responsive.md +551 -551
- package/.claude/contexts/design/shadows.md +522 -522
- package/.claude/contexts/design/typography.md +465 -465
- package/.claude/contexts/domain/README.md +164 -164
- package/.claude/contexts/patterns/agent-coordination.md +388 -388
- package/.claude/contexts/patterns/development-principles.md +513 -513
- package/.claude/contexts/patterns/error-handling.md +478 -478
- package/.claude/contexts/patterns/logging.md +424 -424
- package/.claude/contexts/patterns/tdd-classification.md +516 -516
- package/.claude/contexts/patterns/testing.md +413 -413
- package/.claude/lib/README.md +3 -3
- package/.claude/lib/detailed-guides/taskmaster-analysis.md +1 -1
- package/.claude/lib/task-analyzer.md +144 -0
- package/.claude/lib/tdd-workflow.md +2 -1
- package/.claude/lib/validation-gates.md +484 -484
- package/.claude/settings.local.json +42 -42
- package/.claude/templates/PROJECT_STATUS.template.yml +16 -41
- package/.claude/templates/context-template.md +45 -45
- package/.claude/templates/flags-template.json +42 -42
- package/.claude/templates/phases-sections/accessibility-test.md +17 -17
- package/.claude/templates/phases-sections/api-design.md +37 -37
- package/.claude/templates/phases-sections/backend-tests.md +16 -16
- package/.claude/templates/phases-sections/backend.md +37 -37
- package/.claude/templates/phases-sections/business-logic-validation.md +16 -16
- package/.claude/templates/phases-sections/component-tests.md +17 -17
- package/.claude/templates/phases-sections/contract-backend.md +16 -16
- package/.claude/templates/phases-sections/contract-frontend.md +16 -16
- package/.claude/templates/phases-sections/database.md +35 -35
- package/.claude/templates/phases-sections/e2e-tests.md +16 -16
- package/.claude/templates/phases-sections/fix-implementation.md +17 -17
- package/.claude/templates/phases-sections/frontend-integration.md +18 -18
- package/.claude/templates/phases-sections/manual-flow-test.md +15 -15
- package/.claude/templates/phases-sections/manual-ux-test.md +16 -16
- package/.claude/templates/phases-sections/refactor-implementation.md +17 -17
- package/.claude/templates/phases-sections/refactor.md +16 -16
- package/.claude/templates/phases-sections/regression-tests.md +15 -15
- package/.claude/templates/phases-sections/responsive-test.md +16 -16
- package/.claude/templates/phases-sections/script-implementation.md +43 -43
- package/.claude/templates/phases-sections/test-coverage.md +16 -16
- package/.claude/templates/phases-sections/user-approval.md +14 -14
- package/LICENSE +21 -21
- package/package.json +1 -1
- package/.claude/lib/tdd-classifier.md +0 -345
|
@@ -1,611 +1,611 @@
|
|
|
1
|
-
# Accessibility (a11y)
|
|
2
|
-
|
|
3
|
-
> **Purpose:** Make web applications usable by everyone, including people with disabilities
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## WCAG Standards
|
|
8
|
-
|
|
9
|
-
**Web Content Accessibility Guidelines (WCAG) 2.1**
|
|
10
|
-
|
|
11
|
-
| Level | Compliance | Use Case |
|
|
12
|
-
|-------|------------|----------|
|
|
13
|
-
| **A** | Minimum | Basic accessibility (legal requirement in many countries) |
|
|
14
|
-
| **AA** | Recommended | Standard for most websites (target this) |
|
|
15
|
-
| **AAA** | Enhanced | Government, healthcare, education sites |
|
|
16
|
-
|
|
17
|
-
**Target:** WCAG 2.1 Level AA (industry standard)
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Four Principles (POUR)
|
|
22
|
-
|
|
23
|
-
### 1. Perceivable
|
|
24
|
-
**Users must be able to perceive the information**
|
|
25
|
-
|
|
26
|
-
- ✅ Text alternatives for images
|
|
27
|
-
- ✅ Captions for videos
|
|
28
|
-
- ✅ Color is not the only way to convey meaning
|
|
29
|
-
- ✅ Sufficient color contrast (4.5:1 minimum)
|
|
30
|
-
|
|
31
|
-
### 2. Operable
|
|
32
|
-
**Users must be able to operate the interface**
|
|
33
|
-
|
|
34
|
-
- ✅ Keyboard accessible (all functions work without mouse)
|
|
35
|
-
- ✅ No time limits (or adjustable timers)
|
|
36
|
-
- ✅ No flashing content (seizure risk)
|
|
37
|
-
- ✅ Skip navigation links
|
|
38
|
-
|
|
39
|
-
### 3. Understandable
|
|
40
|
-
**Users must be able to understand the content**
|
|
41
|
-
|
|
42
|
-
- ✅ Readable text (plain language)
|
|
43
|
-
- ✅ Predictable navigation
|
|
44
|
-
- ✅ Clear error messages with suggestions
|
|
45
|
-
- ✅ Labels for all form fields
|
|
46
|
-
|
|
47
|
-
### 4. Robust
|
|
48
|
-
**Content must be robust enough for assistive technologies**
|
|
49
|
-
|
|
50
|
-
- ✅ Valid HTML
|
|
51
|
-
- ✅ Semantic markup
|
|
52
|
-
- ✅ ARIA attributes when needed
|
|
53
|
-
- ✅ Works with screen readers
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## Color Contrast
|
|
58
|
-
|
|
59
|
-
### WCAG Requirements
|
|
60
|
-
|
|
61
|
-
| Text Type | WCAG AA | WCAG AAA |
|
|
62
|
-
|-----------|---------|----------|
|
|
63
|
-
| Normal text (<18pt) | **4.5:1** | 7:1 |
|
|
64
|
-
| Large text (≥18pt or ≥14pt bold) | **3:1** | 4.5:1 |
|
|
65
|
-
| UI components (buttons, inputs) | **3:1** | N/A |
|
|
66
|
-
|
|
67
|
-
### Testing Tools
|
|
68
|
-
|
|
69
|
-
**Browser Extensions:**
|
|
70
|
-
- **WAVE** (WebAIM) - Visual feedback overlay
|
|
71
|
-
- **axe DevTools** - Comprehensive accessibility scanner
|
|
72
|
-
- **Lighthouse** (Chrome DevTools) - Automated audit
|
|
73
|
-
|
|
74
|
-
**Online Tools:**
|
|
75
|
-
- **WebAIM Contrast Checker**: https://webaim.org/resources/contrastchecker/
|
|
76
|
-
- **Contrast Ratio**: https://contrast-ratio.com/
|
|
77
|
-
|
|
78
|
-
### Examples
|
|
79
|
-
|
|
80
|
-
```css
|
|
81
|
-
/* ✅ PASS - 7:1 contrast */
|
|
82
|
-
background: white; /* #FFFFFF */
|
|
83
|
-
color: black; /* #000000 */
|
|
84
|
-
|
|
85
|
-
/* ✅ PASS - 4.6:1 contrast */
|
|
86
|
-
background: #0F2A4A; /* Navy blue */
|
|
87
|
-
color: white; /* #FFFFFF */
|
|
88
|
-
|
|
89
|
-
/* ❌ FAIL - 2.1:1 contrast */
|
|
90
|
-
background: #E0E0E0; /* Light gray */
|
|
91
|
-
color: white; /* #FFFFFF */
|
|
92
|
-
|
|
93
|
-
/* ❌ FAIL - 1.5:1 contrast */
|
|
94
|
-
background: white;
|
|
95
|
-
color: #E0E0E0; /* Light gray text */
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Semantic HTML
|
|
101
|
-
|
|
102
|
-
**Use proper HTML elements for their intended purpose**
|
|
103
|
-
|
|
104
|
-
### DO (Semantic):
|
|
105
|
-
|
|
106
|
-
```html
|
|
107
|
-
<!-- ✅ Navigation -->
|
|
108
|
-
<nav>
|
|
109
|
-
<ul>
|
|
110
|
-
<li><a href="/">Home</a></li>
|
|
111
|
-
<li><a href="/about">About</a></li>
|
|
112
|
-
</ul>
|
|
113
|
-
</nav>
|
|
114
|
-
|
|
115
|
-
<!-- ✅ Main content -->
|
|
116
|
-
<main>
|
|
117
|
-
<article>
|
|
118
|
-
<h1>Article Title</h1>
|
|
119
|
-
<p>Content...</p>
|
|
120
|
-
</article>
|
|
121
|
-
</main>
|
|
122
|
-
|
|
123
|
-
<!-- ✅ Form -->
|
|
124
|
-
<form>
|
|
125
|
-
<label for="email">Email:</label>
|
|
126
|
-
<input type="email" id="email" name="email" required>
|
|
127
|
-
<button type="submit">Submit</button>
|
|
128
|
-
</form>
|
|
129
|
-
|
|
130
|
-
<!-- ✅ Button -->
|
|
131
|
-
<button onclick="doSomething()">Click Me</button>
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### DON'T (Non-semantic):
|
|
135
|
-
|
|
136
|
-
```html
|
|
137
|
-
<!-- ❌ Div soup -->
|
|
138
|
-
<div class="nav">
|
|
139
|
-
<div class="nav-item">
|
|
140
|
-
<span onclick="navigate('/')">Home</span>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<!-- ❌ Divs for everything -->
|
|
145
|
-
<div class="main">
|
|
146
|
-
<div class="article">
|
|
147
|
-
<div class="title">Article Title</div>
|
|
148
|
-
<div class="content">Content...</div>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<!-- ❌ Div as button -->
|
|
153
|
-
<div onclick="doSomething()">Click Me</div>
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
**Why Semantic HTML Matters:**
|
|
157
|
-
- ✅ Screen readers understand structure
|
|
158
|
-
- ✅ Better SEO
|
|
159
|
-
- ✅ Keyboard navigation works automatically
|
|
160
|
-
- ✅ Easier to maintain
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## Keyboard Navigation
|
|
165
|
-
|
|
166
|
-
**All interactive elements must be keyboard accessible**
|
|
167
|
-
|
|
168
|
-
### Tab Order
|
|
169
|
-
|
|
170
|
-
**Standard tab order:**
|
|
171
|
-
1. Links
|
|
172
|
-
2. Buttons
|
|
173
|
-
3. Form inputs (text, select, checkbox, radio)
|
|
174
|
-
4. Textareas
|
|
175
|
-
|
|
176
|
-
**Control tab order:**
|
|
177
|
-
```html
|
|
178
|
-
<!-- Natural tab order (top to bottom) -->
|
|
179
|
-
<button>First</button>
|
|
180
|
-
<button>Second</button>
|
|
181
|
-
<button>Third</button>
|
|
182
|
-
|
|
183
|
-
<!-- Custom tab order (avoid if possible) -->
|
|
184
|
-
<button tabindex="1">First</button>
|
|
185
|
-
<button tabindex="3">Third</button>
|
|
186
|
-
<button tabindex="2">Second</button>
|
|
187
|
-
|
|
188
|
-
<!-- Remove from tab order -->
|
|
189
|
-
<div tabindex="-1">Not focusable</div>
|
|
190
|
-
|
|
191
|
-
<!-- Make div focusable (use semantic HTML instead) -->
|
|
192
|
-
<div tabindex="0" role="button">Focusable div</div>
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Focus Indicators
|
|
196
|
-
|
|
197
|
-
**Always visible focus states:**
|
|
198
|
-
|
|
199
|
-
```css
|
|
200
|
-
/* ✅ Good focus indicator */
|
|
201
|
-
button:focus {
|
|
202
|
-
outline: 2px solid var(--color-primary);
|
|
203
|
-
outline-offset: 2px;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/* ✅ Alternative (box-shadow) */
|
|
207
|
-
input:focus {
|
|
208
|
-
outline: none;
|
|
209
|
-
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.5);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* ❌ NEVER remove focus without replacement */
|
|
213
|
-
*:focus {
|
|
214
|
-
outline: none; /* Makes keyboard navigation impossible */
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Skip Links
|
|
219
|
-
|
|
220
|
-
**Allow keyboard users to skip repetitive navigation:**
|
|
221
|
-
|
|
222
|
-
```html
|
|
223
|
-
<!-- Hidden visually but accessible to screen readers -->
|
|
224
|
-
<a href="#main-content" class="skip-link">
|
|
225
|
-
Skip to main content
|
|
226
|
-
</a>
|
|
227
|
-
|
|
228
|
-
<nav>
|
|
229
|
-
<!-- Navigation items -->
|
|
230
|
-
</nav>
|
|
231
|
-
|
|
232
|
-
<main id="main-content">
|
|
233
|
-
<!-- Main content -->
|
|
234
|
-
</main>
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
```css
|
|
238
|
-
.skip-link {
|
|
239
|
-
position: absolute;
|
|
240
|
-
top: -40px;
|
|
241
|
-
left: 0;
|
|
242
|
-
background: var(--color-primary);
|
|
243
|
-
color: white;
|
|
244
|
-
padding: var(--spacing-2);
|
|
245
|
-
z-index: 1000;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.skip-link:focus {
|
|
249
|
-
top: 0; /* Show on focus */
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## ARIA (Accessible Rich Internet Applications)
|
|
256
|
-
|
|
257
|
-
**Use ARIA when semantic HTML is not enough**
|
|
258
|
-
|
|
259
|
-
### ARIA Roles
|
|
260
|
-
|
|
261
|
-
```html
|
|
262
|
-
<!-- ✅ Use semantic HTML first -->
|
|
263
|
-
<nav>Navigation</nav>
|
|
264
|
-
<main>Main content</main>
|
|
265
|
-
<aside>Sidebar</aside>
|
|
266
|
-
|
|
267
|
-
<!-- ⚠️ Use ARIA when semantic HTML unavailable -->
|
|
268
|
-
<div role="navigation">Navigation</div>
|
|
269
|
-
<div role="main">Main content</div>
|
|
270
|
-
<div role="complementary">Sidebar</div>
|
|
271
|
-
|
|
272
|
-
<!-- Common roles -->
|
|
273
|
-
<div role="button">Custom button</div>
|
|
274
|
-
<div role="alert">Important message</div>
|
|
275
|
-
<div role="dialog">Modal dialog</div>
|
|
276
|
-
<div role="search">Search form</div>
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### ARIA Labels
|
|
280
|
-
|
|
281
|
-
```html
|
|
282
|
-
<!-- Button with no visible text -->
|
|
283
|
-
<button aria-label="Close dialog">
|
|
284
|
-
<svg><!-- X icon --></svg>
|
|
285
|
-
</button>
|
|
286
|
-
|
|
287
|
-
<!-- Icon-only link -->
|
|
288
|
-
<a href="/profile" aria-label="Go to profile">
|
|
289
|
-
<svg><!-- User icon --></svg>
|
|
290
|
-
</a>
|
|
291
|
-
|
|
292
|
-
<!-- Describe element -->
|
|
293
|
-
<button aria-describedby="help-text">
|
|
294
|
-
Submit
|
|
295
|
-
</button>
|
|
296
|
-
<p id="help-text">
|
|
297
|
-
This will send your form data
|
|
298
|
-
</p>
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### ARIA States
|
|
302
|
-
|
|
303
|
-
```html
|
|
304
|
-
<!-- Expanded/collapsed -->
|
|
305
|
-
<button aria-expanded="false" aria-controls="menu">
|
|
306
|
-
Menu
|
|
307
|
-
</button>
|
|
308
|
-
<div id="menu" hidden>
|
|
309
|
-
<!-- Menu items -->
|
|
310
|
-
</div>
|
|
311
|
-
|
|
312
|
-
<!-- Checked (custom checkbox) -->
|
|
313
|
-
<div role="checkbox" aria-checked="true">
|
|
314
|
-
I agree to terms
|
|
315
|
-
</div>
|
|
316
|
-
|
|
317
|
-
<!-- Hidden from screen readers -->
|
|
318
|
-
<div aria-hidden="true">
|
|
319
|
-
Decorative content
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
<!-- Current page in navigation -->
|
|
323
|
-
<nav>
|
|
324
|
-
<a href="/" aria-current="page">Home</a>
|
|
325
|
-
<a href="/about">About</a>
|
|
326
|
-
</nav>
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### ARIA Live Regions
|
|
330
|
-
|
|
331
|
-
**Announce dynamic content changes:**
|
|
332
|
-
|
|
333
|
-
```html
|
|
334
|
-
<!-- Polite (wait for user to finish) -->
|
|
335
|
-
<div aria-live="polite">
|
|
336
|
-
3 new messages
|
|
337
|
-
</div>
|
|
338
|
-
|
|
339
|
-
<!-- Assertive (interrupt immediately) -->
|
|
340
|
-
<div aria-live="assertive" role="alert">
|
|
341
|
-
Error: Form submission failed
|
|
342
|
-
</div>
|
|
343
|
-
|
|
344
|
-
<!-- Atomic (read entire region) -->
|
|
345
|
-
<div aria-live="polite" aria-atomic="true">
|
|
346
|
-
Loading: 45% complete
|
|
347
|
-
</div>
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
## Form Accessibility
|
|
353
|
-
|
|
354
|
-
### Labels
|
|
355
|
-
|
|
356
|
-
**Always associate labels with inputs:**
|
|
357
|
-
|
|
358
|
-
```html
|
|
359
|
-
<!-- ✅ Method 1: for/id -->
|
|
360
|
-
<label for="username">Username:</label>
|
|
361
|
-
<input type="text" id="username" name="username">
|
|
362
|
-
|
|
363
|
-
<!-- ✅ Method 2: Wrapping -->
|
|
364
|
-
<label>
|
|
365
|
-
Email:
|
|
366
|
-
<input type="email" name="email">
|
|
367
|
-
</label>
|
|
368
|
-
|
|
369
|
-
<!-- ❌ No association -->
|
|
370
|
-
<span>Password:</span>
|
|
371
|
-
<input type="password" name="password">
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Required Fields
|
|
375
|
-
|
|
376
|
-
```html
|
|
377
|
-
<!-- ✅ HTML5 required -->
|
|
378
|
-
<label for="email">Email (required):</label>
|
|
379
|
-
<input type="email" id="email" name="email" required>
|
|
380
|
-
|
|
381
|
-
<!-- ✅ ARIA required -->
|
|
382
|
-
<label for="phone">Phone:</label>
|
|
383
|
-
<input type="tel" id="phone" name="phone" aria-required="true">
|
|
384
|
-
|
|
385
|
-
<!-- ✅ Visual indicator + text -->
|
|
386
|
-
<label for="name">
|
|
387
|
-
Name <span aria-label="required">*</span>
|
|
388
|
-
</label>
|
|
389
|
-
<input type="text" id="name" name="name" required>
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Error Messages
|
|
393
|
-
|
|
394
|
-
```html
|
|
395
|
-
<!-- ✅ Associate error with input -->
|
|
396
|
-
<label for="email">Email:</label>
|
|
397
|
-
<input
|
|
398
|
-
type="email"
|
|
399
|
-
id="email"
|
|
400
|
-
name="email"
|
|
401
|
-
aria-invalid="true"
|
|
402
|
-
aria-describedby="email-error"
|
|
403
|
-
>
|
|
404
|
-
<p id="email-error" role="alert">
|
|
405
|
-
Please enter a valid email address
|
|
406
|
-
</p>
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Fieldsets
|
|
410
|
-
|
|
411
|
-
**Group related inputs:**
|
|
412
|
-
|
|
413
|
-
```html
|
|
414
|
-
<fieldset>
|
|
415
|
-
<legend>Shipping Address</legend>
|
|
416
|
-
|
|
417
|
-
<label for="street">Street:</label>
|
|
418
|
-
<input type="text" id="street" name="street">
|
|
419
|
-
|
|
420
|
-
<label for="city">City:</label>
|
|
421
|
-
<input type="text" id="city" name="city">
|
|
422
|
-
</fieldset>
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
---
|
|
426
|
-
|
|
427
|
-
## Image Accessibility
|
|
428
|
-
|
|
429
|
-
### Alt Text
|
|
430
|
-
|
|
431
|
-
```html
|
|
432
|
-
<!-- ✅ Descriptive alt text -->
|
|
433
|
-
<img
|
|
434
|
-
src="sunset.jpg"
|
|
435
|
-
alt="Orange sunset over ocean with waves crashing on rocks"
|
|
436
|
-
>
|
|
437
|
-
|
|
438
|
-
<!-- ✅ Decorative image (empty alt) -->
|
|
439
|
-
<img src="decorative-line.svg" alt="">
|
|
440
|
-
|
|
441
|
-
<!-- ✅ Functional image (describe function) -->
|
|
442
|
-
<button>
|
|
443
|
-
<img src="trash.svg" alt="Delete item">
|
|
444
|
-
</button>
|
|
445
|
-
|
|
446
|
-
<!-- ❌ Missing alt -->
|
|
447
|
-
<img src="important.jpg">
|
|
448
|
-
|
|
449
|
-
<!-- ❌ Redundant alt -->
|
|
450
|
-
<img src="photo-sunset.jpg" alt="Image of sunset">
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### Complex Images
|
|
454
|
-
|
|
455
|
-
```html
|
|
456
|
-
<!-- Chart or infographic -->
|
|
457
|
-
<figure>
|
|
458
|
-
<img
|
|
459
|
-
src="sales-chart.png"
|
|
460
|
-
alt="Bar chart showing quarterly sales"
|
|
461
|
-
aria-describedby="chart-desc"
|
|
462
|
-
>
|
|
463
|
-
<figcaption id="chart-desc">
|
|
464
|
-
Q1: $10k, Q2: $15k, Q3: $12k, Q4: $18k
|
|
465
|
-
</figcaption>
|
|
466
|
-
</figure>
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
---
|
|
470
|
-
|
|
471
|
-
## Modal Dialogs
|
|
472
|
-
|
|
473
|
-
**Accessible modal implementation:**
|
|
474
|
-
|
|
475
|
-
```html
|
|
476
|
-
<div
|
|
477
|
-
role="dialog"
|
|
478
|
-
aria-labelledby="dialog-title"
|
|
479
|
-
aria-modal="true"
|
|
480
|
-
>
|
|
481
|
-
<h2 id="dialog-title">Confirm Action</h2>
|
|
482
|
-
<p>Are you sure you want to delete this item?</p>
|
|
483
|
-
|
|
484
|
-
<button>Cancel</button>
|
|
485
|
-
<button>Delete</button>
|
|
486
|
-
</div>
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
```javascript
|
|
490
|
-
// Focus management
|
|
491
|
-
function openModal(modalId) {
|
|
492
|
-
const modal = document.getElementById(modalId);
|
|
493
|
-
const focusableElements = modal.querySelectorAll(
|
|
494
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
495
|
-
);
|
|
496
|
-
|
|
497
|
-
// Store last focused element
|
|
498
|
-
const lastFocused = document.activeElement;
|
|
499
|
-
|
|
500
|
-
// Show modal
|
|
501
|
-
modal.removeAttribute('hidden');
|
|
502
|
-
|
|
503
|
-
// Focus first element
|
|
504
|
-
focusableElements[0].focus();
|
|
505
|
-
|
|
506
|
-
// Trap focus inside modal
|
|
507
|
-
modal.addEventListener('keydown', (e) => {
|
|
508
|
-
if (e.key === 'Escape') {
|
|
509
|
-
closeModal(modalId);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (e.key === 'Tab') {
|
|
513
|
-
// Trap focus (cycle through focusable elements)
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function closeModal(modalId) {
|
|
519
|
-
const modal = document.getElementById(modalId);
|
|
520
|
-
modal.setAttribute('hidden', '');
|
|
521
|
-
|
|
522
|
-
// Return focus to trigger element
|
|
523
|
-
lastFocused.focus();
|
|
524
|
-
}
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
---
|
|
528
|
-
|
|
529
|
-
## Best Practices
|
|
530
|
-
|
|
531
|
-
### DO:
|
|
532
|
-
- ✅ Use semantic HTML elements
|
|
533
|
-
- ✅ Ensure 4.5:1 contrast for text
|
|
534
|
-
- ✅ Provide text alternatives for images
|
|
535
|
-
- ✅ Make all functionality keyboard accessible
|
|
536
|
-
- ✅ Use visible focus indicators
|
|
537
|
-
- ✅ Associate labels with form inputs
|
|
538
|
-
- ✅ Test with screen readers (NVDA, JAWS, VoiceOver)
|
|
539
|
-
- ✅ Add ARIA attributes when semantic HTML insufficient
|
|
540
|
-
|
|
541
|
-
### DON'T:
|
|
542
|
-
- ❌ Use color alone to convey meaning
|
|
543
|
-
- ❌ Remove focus indicators without replacement
|
|
544
|
-
- ❌ Use divs/spans for interactive elements
|
|
545
|
-
- ❌ Forget alt text on images
|
|
546
|
-
- ❌ Auto-play videos with sound
|
|
547
|
-
- ❌ Use ARIA when semantic HTML exists
|
|
548
|
-
- ❌ Forget to test with keyboard only
|
|
549
|
-
- ❌ Rely only on automated testing (manual testing crucial)
|
|
550
|
-
|
|
551
|
-
---
|
|
552
|
-
|
|
553
|
-
## Testing Checklist
|
|
554
|
-
|
|
555
|
-
### Automated Testing
|
|
556
|
-
|
|
557
|
-
- [ ] Run Lighthouse audit (Chrome DevTools)
|
|
558
|
-
- [ ] Run axe DevTools extension
|
|
559
|
-
- [ ] Run WAVE extension
|
|
560
|
-
- [ ] Check HTML validation (W3C Validator)
|
|
561
|
-
|
|
562
|
-
### Manual Testing
|
|
563
|
-
|
|
564
|
-
- [ ] Navigate entire site with keyboard only (no mouse)
|
|
565
|
-
- [ ] Test with screen reader (NVDA/JAWS/VoiceOver)
|
|
566
|
-
- [ ] Zoom to 200% (content should reflow, not scroll horizontally)
|
|
567
|
-
- [ ] Test color contrast (all text/UI components)
|
|
568
|
-
- [ ] Disable images (alt text should convey meaning)
|
|
569
|
-
- [ ] Test forms (labels, errors, validation)
|
|
570
|
-
|
|
571
|
-
### Device Testing
|
|
572
|
-
|
|
573
|
-
- [ ] Desktop browsers (Chrome, Firefox, Safari, Edge)
|
|
574
|
-
- [ ] Mobile browsers (iOS Safari, Chrome Mobile)
|
|
575
|
-
- [ ] Screen readers (NVDA on Windows, VoiceOver on macOS/iOS)
|
|
576
|
-
- [ ] Keyboard-only navigation
|
|
577
|
-
- [ ] Touch screen navigation
|
|
578
|
-
|
|
579
|
-
---
|
|
580
|
-
|
|
581
|
-
## Quick Reference
|
|
582
|
-
|
|
583
|
-
**Contrast Ratios:**
|
|
584
|
-
| Element | Minimum Ratio |
|
|
585
|
-
|---------|---------------|
|
|
586
|
-
| Normal text | 4.5:1 (AA) |
|
|
587
|
-
| Large text | 3:1 (AA) |
|
|
588
|
-
| UI components | 3:1 (AA) |
|
|
589
|
-
|
|
590
|
-
**Keyboard Shortcuts:**
|
|
591
|
-
| Key | Action |
|
|
592
|
-
|-----|--------|
|
|
593
|
-
| Tab | Move to next focusable element |
|
|
594
|
-
| Shift + Tab | Move to previous element |
|
|
595
|
-
| Enter | Activate button/link |
|
|
596
|
-
| Space | Toggle checkbox, activate button |
|
|
597
|
-
| Esc | Close modal/dialog |
|
|
598
|
-
| Arrow keys | Navigate within component |
|
|
599
|
-
|
|
600
|
-
**ARIA Landmarks:**
|
|
601
|
-
| Role | Semantic HTML |
|
|
602
|
-
|------|---------------|
|
|
603
|
-
| `role="navigation"` | `<nav>` |
|
|
604
|
-
| `role="main"` | `<main>` |
|
|
605
|
-
| `role="complementary"` | `<aside>` |
|
|
606
|
-
| `role="contentinfo"` | `<footer>` |
|
|
607
|
-
| `role="banner"` | `<header>` |
|
|
608
|
-
|
|
609
|
-
---
|
|
610
|
-
|
|
611
|
-
**💡 Remember:** Accessibility benefits everyone, not just people with disabilities!
|
|
1
|
+
# Accessibility (a11y)
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Make web applications usable by everyone, including people with disabilities
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## WCAG Standards
|
|
8
|
+
|
|
9
|
+
**Web Content Accessibility Guidelines (WCAG) 2.1**
|
|
10
|
+
|
|
11
|
+
| Level | Compliance | Use Case |
|
|
12
|
+
|-------|------------|----------|
|
|
13
|
+
| **A** | Minimum | Basic accessibility (legal requirement in many countries) |
|
|
14
|
+
| **AA** | Recommended | Standard for most websites (target this) |
|
|
15
|
+
| **AAA** | Enhanced | Government, healthcare, education sites |
|
|
16
|
+
|
|
17
|
+
**Target:** WCAG 2.1 Level AA (industry standard)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Four Principles (POUR)
|
|
22
|
+
|
|
23
|
+
### 1. Perceivable
|
|
24
|
+
**Users must be able to perceive the information**
|
|
25
|
+
|
|
26
|
+
- ✅ Text alternatives for images
|
|
27
|
+
- ✅ Captions for videos
|
|
28
|
+
- ✅ Color is not the only way to convey meaning
|
|
29
|
+
- ✅ Sufficient color contrast (4.5:1 minimum)
|
|
30
|
+
|
|
31
|
+
### 2. Operable
|
|
32
|
+
**Users must be able to operate the interface**
|
|
33
|
+
|
|
34
|
+
- ✅ Keyboard accessible (all functions work without mouse)
|
|
35
|
+
- ✅ No time limits (or adjustable timers)
|
|
36
|
+
- ✅ No flashing content (seizure risk)
|
|
37
|
+
- ✅ Skip navigation links
|
|
38
|
+
|
|
39
|
+
### 3. Understandable
|
|
40
|
+
**Users must be able to understand the content**
|
|
41
|
+
|
|
42
|
+
- ✅ Readable text (plain language)
|
|
43
|
+
- ✅ Predictable navigation
|
|
44
|
+
- ✅ Clear error messages with suggestions
|
|
45
|
+
- ✅ Labels for all form fields
|
|
46
|
+
|
|
47
|
+
### 4. Robust
|
|
48
|
+
**Content must be robust enough for assistive technologies**
|
|
49
|
+
|
|
50
|
+
- ✅ Valid HTML
|
|
51
|
+
- ✅ Semantic markup
|
|
52
|
+
- ✅ ARIA attributes when needed
|
|
53
|
+
- ✅ Works with screen readers
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Color Contrast
|
|
58
|
+
|
|
59
|
+
### WCAG Requirements
|
|
60
|
+
|
|
61
|
+
| Text Type | WCAG AA | WCAG AAA |
|
|
62
|
+
|-----------|---------|----------|
|
|
63
|
+
| Normal text (<18pt) | **4.5:1** | 7:1 |
|
|
64
|
+
| Large text (≥18pt or ≥14pt bold) | **3:1** | 4.5:1 |
|
|
65
|
+
| UI components (buttons, inputs) | **3:1** | N/A |
|
|
66
|
+
|
|
67
|
+
### Testing Tools
|
|
68
|
+
|
|
69
|
+
**Browser Extensions:**
|
|
70
|
+
- **WAVE** (WebAIM) - Visual feedback overlay
|
|
71
|
+
- **axe DevTools** - Comprehensive accessibility scanner
|
|
72
|
+
- **Lighthouse** (Chrome DevTools) - Automated audit
|
|
73
|
+
|
|
74
|
+
**Online Tools:**
|
|
75
|
+
- **WebAIM Contrast Checker**: https://webaim.org/resources/contrastchecker/
|
|
76
|
+
- **Contrast Ratio**: https://contrast-ratio.com/
|
|
77
|
+
|
|
78
|
+
### Examples
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
/* ✅ PASS - 7:1 contrast */
|
|
82
|
+
background: white; /* #FFFFFF */
|
|
83
|
+
color: black; /* #000000 */
|
|
84
|
+
|
|
85
|
+
/* ✅ PASS - 4.6:1 contrast */
|
|
86
|
+
background: #0F2A4A; /* Navy blue */
|
|
87
|
+
color: white; /* #FFFFFF */
|
|
88
|
+
|
|
89
|
+
/* ❌ FAIL - 2.1:1 contrast */
|
|
90
|
+
background: #E0E0E0; /* Light gray */
|
|
91
|
+
color: white; /* #FFFFFF */
|
|
92
|
+
|
|
93
|
+
/* ❌ FAIL - 1.5:1 contrast */
|
|
94
|
+
background: white;
|
|
95
|
+
color: #E0E0E0; /* Light gray text */
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Semantic HTML
|
|
101
|
+
|
|
102
|
+
**Use proper HTML elements for their intended purpose**
|
|
103
|
+
|
|
104
|
+
### DO (Semantic):
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<!-- ✅ Navigation -->
|
|
108
|
+
<nav>
|
|
109
|
+
<ul>
|
|
110
|
+
<li><a href="/">Home</a></li>
|
|
111
|
+
<li><a href="/about">About</a></li>
|
|
112
|
+
</ul>
|
|
113
|
+
</nav>
|
|
114
|
+
|
|
115
|
+
<!-- ✅ Main content -->
|
|
116
|
+
<main>
|
|
117
|
+
<article>
|
|
118
|
+
<h1>Article Title</h1>
|
|
119
|
+
<p>Content...</p>
|
|
120
|
+
</article>
|
|
121
|
+
</main>
|
|
122
|
+
|
|
123
|
+
<!-- ✅ Form -->
|
|
124
|
+
<form>
|
|
125
|
+
<label for="email">Email:</label>
|
|
126
|
+
<input type="email" id="email" name="email" required>
|
|
127
|
+
<button type="submit">Submit</button>
|
|
128
|
+
</form>
|
|
129
|
+
|
|
130
|
+
<!-- ✅ Button -->
|
|
131
|
+
<button onclick="doSomething()">Click Me</button>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### DON'T (Non-semantic):
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<!-- ❌ Div soup -->
|
|
138
|
+
<div class="nav">
|
|
139
|
+
<div class="nav-item">
|
|
140
|
+
<span onclick="navigate('/')">Home</span>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- ❌ Divs for everything -->
|
|
145
|
+
<div class="main">
|
|
146
|
+
<div class="article">
|
|
147
|
+
<div class="title">Article Title</div>
|
|
148
|
+
<div class="content">Content...</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<!-- ❌ Div as button -->
|
|
153
|
+
<div onclick="doSomething()">Click Me</div>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Why Semantic HTML Matters:**
|
|
157
|
+
- ✅ Screen readers understand structure
|
|
158
|
+
- ✅ Better SEO
|
|
159
|
+
- ✅ Keyboard navigation works automatically
|
|
160
|
+
- ✅ Easier to maintain
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Keyboard Navigation
|
|
165
|
+
|
|
166
|
+
**All interactive elements must be keyboard accessible**
|
|
167
|
+
|
|
168
|
+
### Tab Order
|
|
169
|
+
|
|
170
|
+
**Standard tab order:**
|
|
171
|
+
1. Links
|
|
172
|
+
2. Buttons
|
|
173
|
+
3. Form inputs (text, select, checkbox, radio)
|
|
174
|
+
4. Textareas
|
|
175
|
+
|
|
176
|
+
**Control tab order:**
|
|
177
|
+
```html
|
|
178
|
+
<!-- Natural tab order (top to bottom) -->
|
|
179
|
+
<button>First</button>
|
|
180
|
+
<button>Second</button>
|
|
181
|
+
<button>Third</button>
|
|
182
|
+
|
|
183
|
+
<!-- Custom tab order (avoid if possible) -->
|
|
184
|
+
<button tabindex="1">First</button>
|
|
185
|
+
<button tabindex="3">Third</button>
|
|
186
|
+
<button tabindex="2">Second</button>
|
|
187
|
+
|
|
188
|
+
<!-- Remove from tab order -->
|
|
189
|
+
<div tabindex="-1">Not focusable</div>
|
|
190
|
+
|
|
191
|
+
<!-- Make div focusable (use semantic HTML instead) -->
|
|
192
|
+
<div tabindex="0" role="button">Focusable div</div>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Focus Indicators
|
|
196
|
+
|
|
197
|
+
**Always visible focus states:**
|
|
198
|
+
|
|
199
|
+
```css
|
|
200
|
+
/* ✅ Good focus indicator */
|
|
201
|
+
button:focus {
|
|
202
|
+
outline: 2px solid var(--color-primary);
|
|
203
|
+
outline-offset: 2px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ✅ Alternative (box-shadow) */
|
|
207
|
+
input:focus {
|
|
208
|
+
outline: none;
|
|
209
|
+
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.5);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ❌ NEVER remove focus without replacement */
|
|
213
|
+
*:focus {
|
|
214
|
+
outline: none; /* Makes keyboard navigation impossible */
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Skip Links
|
|
219
|
+
|
|
220
|
+
**Allow keyboard users to skip repetitive navigation:**
|
|
221
|
+
|
|
222
|
+
```html
|
|
223
|
+
<!-- Hidden visually but accessible to screen readers -->
|
|
224
|
+
<a href="#main-content" class="skip-link">
|
|
225
|
+
Skip to main content
|
|
226
|
+
</a>
|
|
227
|
+
|
|
228
|
+
<nav>
|
|
229
|
+
<!-- Navigation items -->
|
|
230
|
+
</nav>
|
|
231
|
+
|
|
232
|
+
<main id="main-content">
|
|
233
|
+
<!-- Main content -->
|
|
234
|
+
</main>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```css
|
|
238
|
+
.skip-link {
|
|
239
|
+
position: absolute;
|
|
240
|
+
top: -40px;
|
|
241
|
+
left: 0;
|
|
242
|
+
background: var(--color-primary);
|
|
243
|
+
color: white;
|
|
244
|
+
padding: var(--spacing-2);
|
|
245
|
+
z-index: 1000;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.skip-link:focus {
|
|
249
|
+
top: 0; /* Show on focus */
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## ARIA (Accessible Rich Internet Applications)
|
|
256
|
+
|
|
257
|
+
**Use ARIA when semantic HTML is not enough**
|
|
258
|
+
|
|
259
|
+
### ARIA Roles
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<!-- ✅ Use semantic HTML first -->
|
|
263
|
+
<nav>Navigation</nav>
|
|
264
|
+
<main>Main content</main>
|
|
265
|
+
<aside>Sidebar</aside>
|
|
266
|
+
|
|
267
|
+
<!-- ⚠️ Use ARIA when semantic HTML unavailable -->
|
|
268
|
+
<div role="navigation">Navigation</div>
|
|
269
|
+
<div role="main">Main content</div>
|
|
270
|
+
<div role="complementary">Sidebar</div>
|
|
271
|
+
|
|
272
|
+
<!-- Common roles -->
|
|
273
|
+
<div role="button">Custom button</div>
|
|
274
|
+
<div role="alert">Important message</div>
|
|
275
|
+
<div role="dialog">Modal dialog</div>
|
|
276
|
+
<div role="search">Search form</div>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### ARIA Labels
|
|
280
|
+
|
|
281
|
+
```html
|
|
282
|
+
<!-- Button with no visible text -->
|
|
283
|
+
<button aria-label="Close dialog">
|
|
284
|
+
<svg><!-- X icon --></svg>
|
|
285
|
+
</button>
|
|
286
|
+
|
|
287
|
+
<!-- Icon-only link -->
|
|
288
|
+
<a href="/profile" aria-label="Go to profile">
|
|
289
|
+
<svg><!-- User icon --></svg>
|
|
290
|
+
</a>
|
|
291
|
+
|
|
292
|
+
<!-- Describe element -->
|
|
293
|
+
<button aria-describedby="help-text">
|
|
294
|
+
Submit
|
|
295
|
+
</button>
|
|
296
|
+
<p id="help-text">
|
|
297
|
+
This will send your form data
|
|
298
|
+
</p>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### ARIA States
|
|
302
|
+
|
|
303
|
+
```html
|
|
304
|
+
<!-- Expanded/collapsed -->
|
|
305
|
+
<button aria-expanded="false" aria-controls="menu">
|
|
306
|
+
Menu
|
|
307
|
+
</button>
|
|
308
|
+
<div id="menu" hidden>
|
|
309
|
+
<!-- Menu items -->
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<!-- Checked (custom checkbox) -->
|
|
313
|
+
<div role="checkbox" aria-checked="true">
|
|
314
|
+
I agree to terms
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<!-- Hidden from screen readers -->
|
|
318
|
+
<div aria-hidden="true">
|
|
319
|
+
Decorative content
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
<!-- Current page in navigation -->
|
|
323
|
+
<nav>
|
|
324
|
+
<a href="/" aria-current="page">Home</a>
|
|
325
|
+
<a href="/about">About</a>
|
|
326
|
+
</nav>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### ARIA Live Regions
|
|
330
|
+
|
|
331
|
+
**Announce dynamic content changes:**
|
|
332
|
+
|
|
333
|
+
```html
|
|
334
|
+
<!-- Polite (wait for user to finish) -->
|
|
335
|
+
<div aria-live="polite">
|
|
336
|
+
3 new messages
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<!-- Assertive (interrupt immediately) -->
|
|
340
|
+
<div aria-live="assertive" role="alert">
|
|
341
|
+
Error: Form submission failed
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<!-- Atomic (read entire region) -->
|
|
345
|
+
<div aria-live="polite" aria-atomic="true">
|
|
346
|
+
Loading: 45% complete
|
|
347
|
+
</div>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Form Accessibility
|
|
353
|
+
|
|
354
|
+
### Labels
|
|
355
|
+
|
|
356
|
+
**Always associate labels with inputs:**
|
|
357
|
+
|
|
358
|
+
```html
|
|
359
|
+
<!-- ✅ Method 1: for/id -->
|
|
360
|
+
<label for="username">Username:</label>
|
|
361
|
+
<input type="text" id="username" name="username">
|
|
362
|
+
|
|
363
|
+
<!-- ✅ Method 2: Wrapping -->
|
|
364
|
+
<label>
|
|
365
|
+
Email:
|
|
366
|
+
<input type="email" name="email">
|
|
367
|
+
</label>
|
|
368
|
+
|
|
369
|
+
<!-- ❌ No association -->
|
|
370
|
+
<span>Password:</span>
|
|
371
|
+
<input type="password" name="password">
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Required Fields
|
|
375
|
+
|
|
376
|
+
```html
|
|
377
|
+
<!-- ✅ HTML5 required -->
|
|
378
|
+
<label for="email">Email (required):</label>
|
|
379
|
+
<input type="email" id="email" name="email" required>
|
|
380
|
+
|
|
381
|
+
<!-- ✅ ARIA required -->
|
|
382
|
+
<label for="phone">Phone:</label>
|
|
383
|
+
<input type="tel" id="phone" name="phone" aria-required="true">
|
|
384
|
+
|
|
385
|
+
<!-- ✅ Visual indicator + text -->
|
|
386
|
+
<label for="name">
|
|
387
|
+
Name <span aria-label="required">*</span>
|
|
388
|
+
</label>
|
|
389
|
+
<input type="text" id="name" name="name" required>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Error Messages
|
|
393
|
+
|
|
394
|
+
```html
|
|
395
|
+
<!-- ✅ Associate error with input -->
|
|
396
|
+
<label for="email">Email:</label>
|
|
397
|
+
<input
|
|
398
|
+
type="email"
|
|
399
|
+
id="email"
|
|
400
|
+
name="email"
|
|
401
|
+
aria-invalid="true"
|
|
402
|
+
aria-describedby="email-error"
|
|
403
|
+
>
|
|
404
|
+
<p id="email-error" role="alert">
|
|
405
|
+
Please enter a valid email address
|
|
406
|
+
</p>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Fieldsets
|
|
410
|
+
|
|
411
|
+
**Group related inputs:**
|
|
412
|
+
|
|
413
|
+
```html
|
|
414
|
+
<fieldset>
|
|
415
|
+
<legend>Shipping Address</legend>
|
|
416
|
+
|
|
417
|
+
<label for="street">Street:</label>
|
|
418
|
+
<input type="text" id="street" name="street">
|
|
419
|
+
|
|
420
|
+
<label for="city">City:</label>
|
|
421
|
+
<input type="text" id="city" name="city">
|
|
422
|
+
</fieldset>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Image Accessibility
|
|
428
|
+
|
|
429
|
+
### Alt Text
|
|
430
|
+
|
|
431
|
+
```html
|
|
432
|
+
<!-- ✅ Descriptive alt text -->
|
|
433
|
+
<img
|
|
434
|
+
src="sunset.jpg"
|
|
435
|
+
alt="Orange sunset over ocean with waves crashing on rocks"
|
|
436
|
+
>
|
|
437
|
+
|
|
438
|
+
<!-- ✅ Decorative image (empty alt) -->
|
|
439
|
+
<img src="decorative-line.svg" alt="">
|
|
440
|
+
|
|
441
|
+
<!-- ✅ Functional image (describe function) -->
|
|
442
|
+
<button>
|
|
443
|
+
<img src="trash.svg" alt="Delete item">
|
|
444
|
+
</button>
|
|
445
|
+
|
|
446
|
+
<!-- ❌ Missing alt -->
|
|
447
|
+
<img src="important.jpg">
|
|
448
|
+
|
|
449
|
+
<!-- ❌ Redundant alt -->
|
|
450
|
+
<img src="photo-sunset.jpg" alt="Image of sunset">
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Complex Images
|
|
454
|
+
|
|
455
|
+
```html
|
|
456
|
+
<!-- Chart or infographic -->
|
|
457
|
+
<figure>
|
|
458
|
+
<img
|
|
459
|
+
src="sales-chart.png"
|
|
460
|
+
alt="Bar chart showing quarterly sales"
|
|
461
|
+
aria-describedby="chart-desc"
|
|
462
|
+
>
|
|
463
|
+
<figcaption id="chart-desc">
|
|
464
|
+
Q1: $10k, Q2: $15k, Q3: $12k, Q4: $18k
|
|
465
|
+
</figcaption>
|
|
466
|
+
</figure>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Modal Dialogs
|
|
472
|
+
|
|
473
|
+
**Accessible modal implementation:**
|
|
474
|
+
|
|
475
|
+
```html
|
|
476
|
+
<div
|
|
477
|
+
role="dialog"
|
|
478
|
+
aria-labelledby="dialog-title"
|
|
479
|
+
aria-modal="true"
|
|
480
|
+
>
|
|
481
|
+
<h2 id="dialog-title">Confirm Action</h2>
|
|
482
|
+
<p>Are you sure you want to delete this item?</p>
|
|
483
|
+
|
|
484
|
+
<button>Cancel</button>
|
|
485
|
+
<button>Delete</button>
|
|
486
|
+
</div>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
// Focus management
|
|
491
|
+
function openModal(modalId) {
|
|
492
|
+
const modal = document.getElementById(modalId);
|
|
493
|
+
const focusableElements = modal.querySelectorAll(
|
|
494
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Store last focused element
|
|
498
|
+
const lastFocused = document.activeElement;
|
|
499
|
+
|
|
500
|
+
// Show modal
|
|
501
|
+
modal.removeAttribute('hidden');
|
|
502
|
+
|
|
503
|
+
// Focus first element
|
|
504
|
+
focusableElements[0].focus();
|
|
505
|
+
|
|
506
|
+
// Trap focus inside modal
|
|
507
|
+
modal.addEventListener('keydown', (e) => {
|
|
508
|
+
if (e.key === 'Escape') {
|
|
509
|
+
closeModal(modalId);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (e.key === 'Tab') {
|
|
513
|
+
// Trap focus (cycle through focusable elements)
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function closeModal(modalId) {
|
|
519
|
+
const modal = document.getElementById(modalId);
|
|
520
|
+
modal.setAttribute('hidden', '');
|
|
521
|
+
|
|
522
|
+
// Return focus to trigger element
|
|
523
|
+
lastFocused.focus();
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Best Practices
|
|
530
|
+
|
|
531
|
+
### DO:
|
|
532
|
+
- ✅ Use semantic HTML elements
|
|
533
|
+
- ✅ Ensure 4.5:1 contrast for text
|
|
534
|
+
- ✅ Provide text alternatives for images
|
|
535
|
+
- ✅ Make all functionality keyboard accessible
|
|
536
|
+
- ✅ Use visible focus indicators
|
|
537
|
+
- ✅ Associate labels with form inputs
|
|
538
|
+
- ✅ Test with screen readers (NVDA, JAWS, VoiceOver)
|
|
539
|
+
- ✅ Add ARIA attributes when semantic HTML insufficient
|
|
540
|
+
|
|
541
|
+
### DON'T:
|
|
542
|
+
- ❌ Use color alone to convey meaning
|
|
543
|
+
- ❌ Remove focus indicators without replacement
|
|
544
|
+
- ❌ Use divs/spans for interactive elements
|
|
545
|
+
- ❌ Forget alt text on images
|
|
546
|
+
- ❌ Auto-play videos with sound
|
|
547
|
+
- ❌ Use ARIA when semantic HTML exists
|
|
548
|
+
- ❌ Forget to test with keyboard only
|
|
549
|
+
- ❌ Rely only on automated testing (manual testing crucial)
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Testing Checklist
|
|
554
|
+
|
|
555
|
+
### Automated Testing
|
|
556
|
+
|
|
557
|
+
- [ ] Run Lighthouse audit (Chrome DevTools)
|
|
558
|
+
- [ ] Run axe DevTools extension
|
|
559
|
+
- [ ] Run WAVE extension
|
|
560
|
+
- [ ] Check HTML validation (W3C Validator)
|
|
561
|
+
|
|
562
|
+
### Manual Testing
|
|
563
|
+
|
|
564
|
+
- [ ] Navigate entire site with keyboard only (no mouse)
|
|
565
|
+
- [ ] Test with screen reader (NVDA/JAWS/VoiceOver)
|
|
566
|
+
- [ ] Zoom to 200% (content should reflow, not scroll horizontally)
|
|
567
|
+
- [ ] Test color contrast (all text/UI components)
|
|
568
|
+
- [ ] Disable images (alt text should convey meaning)
|
|
569
|
+
- [ ] Test forms (labels, errors, validation)
|
|
570
|
+
|
|
571
|
+
### Device Testing
|
|
572
|
+
|
|
573
|
+
- [ ] Desktop browsers (Chrome, Firefox, Safari, Edge)
|
|
574
|
+
- [ ] Mobile browsers (iOS Safari, Chrome Mobile)
|
|
575
|
+
- [ ] Screen readers (NVDA on Windows, VoiceOver on macOS/iOS)
|
|
576
|
+
- [ ] Keyboard-only navigation
|
|
577
|
+
- [ ] Touch screen navigation
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Quick Reference
|
|
582
|
+
|
|
583
|
+
**Contrast Ratios:**
|
|
584
|
+
| Element | Minimum Ratio |
|
|
585
|
+
|---------|---------------|
|
|
586
|
+
| Normal text | 4.5:1 (AA) |
|
|
587
|
+
| Large text | 3:1 (AA) |
|
|
588
|
+
| UI components | 3:1 (AA) |
|
|
589
|
+
|
|
590
|
+
**Keyboard Shortcuts:**
|
|
591
|
+
| Key | Action |
|
|
592
|
+
|-----|--------|
|
|
593
|
+
| Tab | Move to next focusable element |
|
|
594
|
+
| Shift + Tab | Move to previous element |
|
|
595
|
+
| Enter | Activate button/link |
|
|
596
|
+
| Space | Toggle checkbox, activate button |
|
|
597
|
+
| Esc | Close modal/dialog |
|
|
598
|
+
| Arrow keys | Navigate within component |
|
|
599
|
+
|
|
600
|
+
**ARIA Landmarks:**
|
|
601
|
+
| Role | Semantic HTML |
|
|
602
|
+
|------|---------------|
|
|
603
|
+
| `role="navigation"` | `<nav>` |
|
|
604
|
+
| `role="main"` | `<main>` |
|
|
605
|
+
| `role="complementary"` | `<aside>` |
|
|
606
|
+
| `role="contentinfo"` | `<footer>` |
|
|
607
|
+
| `role="banner"` | `<header>` |
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
**💡 Remember:** Accessibility benefits everyone, not just people with disabilities!
|