caniarun 0.1.0__tar.gz

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.
Files changed (104) hide show
  1. caniarun-0.1.0/.agents/skills/accessibility/SKILL.md +440 -0
  2. caniarun-0.1.0/.agents/skills/accessibility/references/A11Y-PATTERNS.md +233 -0
  3. caniarun-0.1.0/.agents/skills/accessibility/references/WCAG.md +191 -0
  4. caniarun-0.1.0/.agents/skills/accessibility-a11y/SKILL.md +131 -0
  5. caniarun-0.1.0/.agents/skills/astro/SKILL.md +140 -0
  6. caniarun-0.1.0/.agents/skills/astro-framework/AGENTS.md +857 -0
  7. caniarun-0.1.0/.agents/skills/astro-framework/SKILL.md +162 -0
  8. caniarun-0.1.0/.agents/skills/astro-framework/references/actions.md +386 -0
  9. caniarun-0.1.0/.agents/skills/astro-framework/references/client-directives.md +215 -0
  10. caniarun-0.1.0/.agents/skills/astro-framework/references/components.md +332 -0
  11. caniarun-0.1.0/.agents/skills/astro-framework/references/configuration.md +429 -0
  12. caniarun-0.1.0/.agents/skills/astro-framework/references/content-collections.md +358 -0
  13. caniarun-0.1.0/.agents/skills/astro-framework/references/images.md +381 -0
  14. caniarun-0.1.0/.agents/skills/astro-framework/references/middleware.md +350 -0
  15. caniarun-0.1.0/.agents/skills/astro-framework/references/routing.md +330 -0
  16. caniarun-0.1.0/.agents/skills/astro-framework/references/ssr-adapters.md +356 -0
  17. caniarun-0.1.0/.agents/skills/astro-framework/references/styling.md +445 -0
  18. caniarun-0.1.0/.agents/skills/astro-framework/references/view-transitions.md +390 -0
  19. caniarun-0.1.0/.agents/skills/astro-framework/rules/astro-components.rule.md +111 -0
  20. caniarun-0.1.0/.agents/skills/astro-framework/rules/astro-images.rule.md +151 -0
  21. caniarun-0.1.0/.agents/skills/astro-framework/rules/astro-routing.rule.md +142 -0
  22. caniarun-0.1.0/.agents/skills/astro-framework/rules/astro-ssr.rule.md +140 -0
  23. caniarun-0.1.0/.agents/skills/astro-framework/rules/astro-typescript.rule.md +144 -0
  24. caniarun-0.1.0/.agents/skills/astro-framework/rules/client-hydration.rule.md +96 -0
  25. caniarun-0.1.0/.agents/skills/astro-framework/rules/content-collections.rule.md +116 -0
  26. caniarun-0.1.0/.agents/skills/bash-defensive-patterns/SKILL.md +533 -0
  27. caniarun-0.1.0/.agents/skills/deploy-to-vercel/SKILL.md +296 -0
  28. caniarun-0.1.0/.agents/skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
  29. caniarun-0.1.0/.agents/skills/deploy-to-vercel/resources/deploy.sh +301 -0
  30. caniarun-0.1.0/.agents/skills/emil-design-eng/SKILL.md +671 -0
  31. caniarun-0.1.0/.agents/skills/frontend-design/LICENSE.txt +177 -0
  32. caniarun-0.1.0/.agents/skills/frontend-design/SKILL.md +42 -0
  33. caniarun-0.1.0/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  34. caniarun-0.1.0/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  35. caniarun-0.1.0/.agents/skills/nodejs-best-practices/SKILL.md +338 -0
  36. caniarun-0.1.0/.agents/skills/seo/SKILL.md +513 -0
  37. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/SKILL.md +152 -0
  38. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/accessibility.md +167 -0
  39. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/animations.md +141 -0
  40. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/component-patterns.md +169 -0
  41. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/configuration.md +198 -0
  42. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/layout-patterns.md +222 -0
  43. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/performance.md +119 -0
  44. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/reference.md +508 -0
  45. caniarun-0.1.0/.agents/skills/tailwind-css-patterns/references/responsive-design.md +159 -0
  46. caniarun-0.1.0/.agents/skills/typescript-advanced-types/SKILL.md +717 -0
  47. caniarun-0.1.0/.agents/skills/web-design-guidelines/SKILL.md +39 -0
  48. caniarun-0.1.0/.github/workflows/workflow.yml +34 -0
  49. caniarun-0.1.0/.gitignore +24 -0
  50. caniarun-0.1.0/.vscode/extensions.json +4 -0
  51. caniarun-0.1.0/.vscode/launch.json +11 -0
  52. caniarun-0.1.0/AGENTS.md +22 -0
  53. caniarun-0.1.0/HOW_IT_WORKS.md +75 -0
  54. caniarun-0.1.0/PKG-INFO +62 -0
  55. caniarun-0.1.0/README.md +47 -0
  56. caniarun-0.1.0/RESULTS.md +44 -0
  57. caniarun-0.1.0/caniarun.log +80 -0
  58. caniarun-0.1.0/caniarun.sh +3 -0
  59. caniarun-0.1.0/pyproject.toml +25 -0
  60. caniarun-0.1.0/refactor.py +124 -0
  61. caniarun-0.1.0/sample.log +80 -0
  62. caniarun-0.1.0/scratch.py +22 -0
  63. caniarun-0.1.0/skills-lock.json +55 -0
  64. caniarun-0.1.0/src/caniarun/__init__.py +1 -0
  65. caniarun-0.1.0/src/caniarun/__main__.py +4 -0
  66. caniarun-0.1.0/src/caniarun/cli.py +181 -0
  67. caniarun-0.1.0/src/caniarun/compat.py +158 -0
  68. caniarun-0.1.0/src/caniarun/gpu_db.py +264 -0
  69. caniarun-0.1.0/src/caniarun/hardware.py +211 -0
  70. caniarun-0.1.0/src/caniarun/log_analyzer.py +161 -0
  71. caniarun-0.1.0/src/caniarun/log_emitter.py +85 -0
  72. caniarun-0.1.0/src/caniarun/models/__init__.py +49 -0
  73. caniarun-0.1.0/src/caniarun/models/command.py +29 -0
  74. caniarun-0.1.0/src/caniarun/models/core.py +31 -0
  75. caniarun-0.1.0/src/caniarun/models/deepseek.py +188 -0
  76. caniarun-0.1.0/src/caniarun/models/exaone.py +28 -0
  77. caniarun-0.1.0/src/caniarun/models/gemma_1.py +175 -0
  78. caniarun-0.1.0/src/caniarun/models/gemma_2.py +181 -0
  79. caniarun-0.1.0/src/caniarun/models/gemma_3.py +28 -0
  80. caniarun-0.1.0/src/caniarun/models/glm.py +29 -0
  81. caniarun-0.1.0/src/caniarun/models/gpt_oss.py +60 -0
  82. caniarun-0.1.0/src/caniarun/models/kimi.py +32 -0
  83. caniarun-0.1.0/src/caniarun/models/lfm.py +31 -0
  84. caniarun-0.1.0/src/caniarun/models/llama_1.py +184 -0
  85. caniarun-0.1.0/src/caniarun/models/llama_2.py +56 -0
  86. caniarun-0.1.0/src/caniarun/models/mistral_1.py +183 -0
  87. caniarun-0.1.0/src/caniarun/models/mistral_2.py +31 -0
  88. caniarun-0.1.0/src/caniarun/models/nemotron.py +55 -0
  89. caniarun-0.1.0/src/caniarun/models/olmo.py +28 -0
  90. caniarun-0.1.0/src/caniarun/models/phi.py +80 -0
  91. caniarun-0.1.0/src/caniarun/models/qwen_1.py +178 -0
  92. caniarun-0.1.0/src/caniarun/models/qwen_2.py +180 -0
  93. caniarun-0.1.0/src/caniarun/models/qwen_3.py +187 -0
  94. caniarun-0.1.0/src/caniarun/models/qwen_4.py +85 -0
  95. caniarun-0.1.0/src/caniarun/models/smollm.py +28 -0
  96. caniarun-0.1.0/src/caniarun/models_backup.py +2002 -0
  97. caniarun-0.1.0/src/caniarun/output.py +418 -0
  98. caniarun-0.1.0/src/caniarun/share.py +50 -0
  99. caniarun-0.1.0/tests/sample_clean.log +8 -0
  100. caniarun-0.1.0/tests/sample_dirty.log +12 -0
  101. caniarun-0.1.0/tests/test_analyzer.py +67 -0
  102. caniarun-0.1.0/tests/test_compat.py +37 -0
  103. caniarun-0.1.0/tests/test_hardware.py +22 -0
  104. caniarun-0.1.0/tests/test_models.py +23 -0
@@ -0,0 +1,440 @@
1
+ ---
2
+ name: accessibility
3
+ description: Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".
4
+ license: MIT
5
+ metadata:
6
+ author: web-quality-skills
7
+ version: "1.1"
8
+ ---
9
+
10
+ # Accessibility (a11y)
11
+
12
+ Comprehensive accessibility guidelines based on WCAG 2.2 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities.
13
+
14
+ ## WCAG Principles: POUR
15
+
16
+ | Principle | Description |
17
+ |-----------|-------------|
18
+ | **P**erceivable | Content can be perceived through different senses |
19
+ | **O**perable | Interface can be operated by all users |
20
+ | **U**nderstandable | Content and interface are understandable |
21
+ | **R**obust | Content works with assistive technologies |
22
+
23
+ ## Conformance levels
24
+
25
+ | Level | Requirement | Target |
26
+ |-------|-------------|--------|
27
+ | **A** | Minimum accessibility | Must pass |
28
+ | **AA** | Standard compliance | Should pass (legal requirement in many jurisdictions) |
29
+ | **AAA** | Enhanced accessibility | Nice to have |
30
+
31
+ ---
32
+
33
+ ## Perceivable
34
+
35
+ ### Text alternatives (1.1)
36
+
37
+ **Images require alt text:**
38
+ ```html
39
+ <!-- ❌ Missing alt -->
40
+ <img src="chart.png">
41
+
42
+ <!-- ✅ Descriptive alt -->
43
+ <img src="chart.png" alt="Bar chart showing 40% increase in Q3 sales">
44
+
45
+ <!-- ✅ Decorative image (empty alt) -->
46
+ <img src="decorative-border.png" alt="" role="presentation">
47
+
48
+ <!-- ✅ Complex image with longer description -->
49
+ <figure>
50
+ <img src="infographic.png" alt="2024 market trends infographic"
51
+ aria-describedby="infographic-desc">
52
+ <figcaption id="infographic-desc">
53
+ <!-- Detailed description -->
54
+ </figcaption>
55
+ </figure>
56
+ ```
57
+
58
+ **Icon buttons need accessible names:**
59
+ ```html
60
+ <!-- ❌ No accessible name -->
61
+ <button><svg><!-- menu icon --></svg></button>
62
+
63
+ <!-- ✅ Using aria-label -->
64
+ <button aria-label="Open menu">
65
+ <svg aria-hidden="true"><!-- menu icon --></svg>
66
+ </button>
67
+
68
+ <!-- ✅ Using visually hidden text -->
69
+ <button>
70
+ <svg aria-hidden="true"><!-- menu icon --></svg>
71
+ <span class="visually-hidden">Open menu</span>
72
+ </button>
73
+ ```
74
+
75
+ **Visually hidden class:**
76
+ ```css
77
+ .visually-hidden {
78
+ position: absolute;
79
+ width: 1px;
80
+ height: 1px;
81
+ padding: 0;
82
+ margin: -1px;
83
+ overflow: hidden;
84
+ clip: rect(0, 0, 0, 0);
85
+ white-space: nowrap;
86
+ border: 0;
87
+ }
88
+ ```
89
+
90
+ ### Color contrast (1.4.3, 1.4.6)
91
+
92
+ | Text Size | AA minimum | AAA enhanced |
93
+ |-----------|------------|--------------|
94
+ | Normal text (< 18px / < 14px bold) | 4.5:1 | 7:1 |
95
+ | Large text (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 |
96
+ | UI components & graphics | 3:1 | 3:1 |
97
+
98
+ ```css
99
+ /* ❌ Low contrast (2.5:1) */
100
+ .low-contrast {
101
+ color: #999;
102
+ background: #fff;
103
+ }
104
+
105
+ /* ✅ Sufficient contrast (7:1) */
106
+ .high-contrast {
107
+ color: #333;
108
+ background: #fff;
109
+ }
110
+
111
+ /* ✅ Focus states need contrast too */
112
+ :focus-visible {
113
+ outline: 2px solid #005fcc;
114
+ outline-offset: 2px;
115
+ }
116
+ ```
117
+
118
+ **Don't rely on color alone:**
119
+ ```html
120
+ <!-- ❌ Only color indicates error -->
121
+ <input class="error-border">
122
+ <style>.error-border { border-color: red; }</style>
123
+
124
+ <!-- ✅ Color + icon + text -->
125
+ <div class="field-error">
126
+ <input aria-invalid="true" aria-describedby="email-error">
127
+ <span id="email-error" class="error-message">
128
+ <svg aria-hidden="true"><!-- error icon --></svg>
129
+ Please enter a valid email address
130
+ </span>
131
+ </div>
132
+ ```
133
+
134
+ ### Media alternatives (1.2)
135
+
136
+ ```html
137
+ <!-- Video with captions -->
138
+ <video controls>
139
+ <source src="video.mp4" type="video/mp4">
140
+ <track kind="captions" src="captions.vtt" srclang="en" label="English" default>
141
+ <track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions">
142
+ </video>
143
+
144
+ <!-- Audio with transcript -->
145
+ <audio controls>
146
+ <source src="podcast.mp3" type="audio/mp3">
147
+ </audio>
148
+ <details>
149
+ <summary>Transcript</summary>
150
+ <p>Full transcript text...</p>
151
+ </details>
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Operable
157
+
158
+ ### Keyboard accessible (2.1)
159
+
160
+ **All functionality must be keyboard accessible:**
161
+ ```javascript
162
+ // ❌ Only handles click
163
+ element.addEventListener('click', handleAction);
164
+
165
+ // ✅ Handles both click and keyboard
166
+ element.addEventListener('click', handleAction);
167
+ element.addEventListener('keydown', (e) => {
168
+ if (e.key === 'Enter' || e.key === ' ') {
169
+ e.preventDefault();
170
+ handleAction();
171
+ }
172
+ });
173
+ ```
174
+
175
+ **No keyboard traps.** Users must be able to Tab into and out of every component. Use the [modal focus trap pattern](references/A11Y-PATTERNS.md#modal-focus-trap) for dialogs—the native `<dialog>` element handles this automatically.
176
+
177
+ ### Focus visible (2.4.7)
178
+
179
+ ```css
180
+ /* ❌ Never remove focus outlines */
181
+ *:focus { outline: none; }
182
+
183
+ /* ✅ Use :focus-visible for keyboard-only focus */
184
+ :focus {
185
+ outline: none;
186
+ }
187
+
188
+ :focus-visible {
189
+ outline: 2px solid #005fcc;
190
+ outline-offset: 2px;
191
+ }
192
+
193
+ /* ✅ Or custom focus styles */
194
+ button:focus-visible {
195
+ box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
196
+ }
197
+ ```
198
+
199
+ ### Focus not obscured (2.4.11) — new in 2.2
200
+
201
+ When an element receives keyboard focus, it must not be entirely hidden by other author-created content such as sticky headers, footers, or overlapping panels. At Level AAA (2.4.12), no part of the focused element may be hidden.
202
+
203
+ ```css
204
+ /* ✅ Account for sticky headers when scrolling to focused elements */
205
+ :target {
206
+ scroll-margin-top: 80px;
207
+ }
208
+
209
+ /* ✅ Ensure focused items clear fixed/sticky bars */
210
+ :focus {
211
+ scroll-margin-top: 80px;
212
+ scroll-margin-bottom: 60px;
213
+ }
214
+ ```
215
+
216
+ ### Skip links (2.4.1)
217
+
218
+ Provide a skip link so keyboard users can bypass repetitive navigation. See the [skip link pattern](references/A11Y-PATTERNS.md#skip-link) for full markup and styles.
219
+
220
+ ### Target size (2.5.8) — new in 2.2
221
+
222
+ Interactive targets must be at least **24 × 24 CSS pixels** (AA). Exceptions: inline text links, elements where the browser controls the size, and targets where a 24px circle centered on the bounding box does not overlap another target.
223
+
224
+ ```css
225
+ /* ✅ Minimum target size */
226
+ button,
227
+ [role="button"],
228
+ input[type="checkbox"] + label,
229
+ input[type="radio"] + label {
230
+ min-width: 24px;
231
+ min-height: 24px;
232
+ }
233
+
234
+ /* ✅ Comfortable target size (recommended 44×44) */
235
+ .touch-target {
236
+ min-width: 44px;
237
+ min-height: 44px;
238
+ display: inline-flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ }
242
+ ```
243
+
244
+ ### Dragging movements (2.5.7) — new in 2.2
245
+
246
+ Any action that requires dragging must have a single-pointer alternative (e.g., buttons, inputs). See the [dragging movements pattern](references/A11Y-PATTERNS.md#dragging-movements) for a sortable-list example.
247
+
248
+ ### Timing (2.2)
249
+
250
+ ```javascript
251
+ // Allow users to extend time limits
252
+ function showSessionWarning() {
253
+ const modal = createModal({
254
+ title: 'Session Expiring',
255
+ content: 'Your session will expire in 2 minutes.',
256
+ actions: [
257
+ { label: 'Extend session', action: extendSession },
258
+ { label: 'Log out', action: logout }
259
+ ],
260
+ timeout: 120000
261
+ });
262
+ }
263
+ ```
264
+
265
+ ### Motion (2.3)
266
+
267
+ ```css
268
+ /* Respect reduced motion preference */
269
+ @media (prefers-reduced-motion: reduce) {
270
+ *,
271
+ *::before,
272
+ *::after {
273
+ animation-duration: 0.01ms !important;
274
+ animation-iteration-count: 1 !important;
275
+ transition-duration: 0.01ms !important;
276
+ scroll-behavior: auto !important;
277
+ }
278
+ }
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Understandable
284
+
285
+ ### Page language (3.1.1)
286
+
287
+ ```html
288
+ <!-- ❌ No language specified -->
289
+ <html>
290
+
291
+ <!-- ✅ Language specified -->
292
+ <html lang="en">
293
+
294
+ <!-- ✅ Language changes within page -->
295
+ <p>The French word for hello is <span lang="fr">bonjour</span>.</p>
296
+ ```
297
+
298
+ ### Consistent navigation (3.2.3)
299
+
300
+ ```html
301
+ <!-- Navigation should be consistent across pages -->
302
+ <nav aria-label="Main">
303
+ <ul>
304
+ <li><a href="/" aria-current="page">Home</a></li>
305
+ <li><a href="/products">Products</a></li>
306
+ <li><a href="/about">About</a></li>
307
+ </ul>
308
+ </nav>
309
+ ```
310
+
311
+ ### Consistent help (3.2.6) — new in 2.2
312
+
313
+ If a help mechanism (contact info, chat widget, FAQ link, self-help option) is repeated across multiple pages, it must appear in the **same relative order** each time. Users who rely on consistent placement shouldn't have to hunt for help on every page.
314
+
315
+ ### Form labels (3.3.2)
316
+
317
+ Every input needs a programmatically associated label. See the [form labels pattern](references/A11Y-PATTERNS.md#form-labels) for explicit, implicit, and instructional examples.
318
+
319
+ ### Error handling (3.3.1, 3.3.3)
320
+
321
+ Announce errors to screen readers with `role="alert"` or `aria-live`, set `aria-invalid="true"` on invalid fields, and focus the first error on submit. See the [error handling pattern](references/A11Y-PATTERNS.md#error-handling) for full markup and JS.
322
+
323
+ ### Redundant entry (3.3.7) — new in 2.2
324
+
325
+ Don't force users to re-enter information they already provided in the same session. Auto-populate from earlier steps, or let users select from previously entered values. Exceptions: security re-confirmation and content that has expired.
326
+
327
+ ```html
328
+ <!-- ✅ Auto-fill shipping address from billing -->
329
+ <fieldset>
330
+ <legend>Shipping address</legend>
331
+ <label>
332
+ <input type="checkbox" id="same-as-billing" checked>
333
+ Same as billing address
334
+ </label>
335
+ <!-- Fields auto-populated when checked -->
336
+ </fieldset>
337
+ ```
338
+
339
+ ### Accessible authentication (3.3.8) — new in 2.2
340
+
341
+ Login flows must not rely on cognitive function tests (e.g., remembering a password, solving a puzzle) unless at least one of:
342
+ - A copy-paste or autofill mechanism is available
343
+ - An alternative method exists (e.g., passkey, SSO, email link)
344
+ - The test uses object recognition or personal content (AA only; AAA removes this exception)
345
+
346
+ ```html
347
+ <!-- ✅ Allow paste in password fields -->
348
+ <input type="password" id="password" autocomplete="current-password">
349
+
350
+ <!-- ✅ Offer passwordless alternatives -->
351
+ <button type="button">Sign in with passkey</button>
352
+ <button type="button">Email me a login link</button>
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Robust
358
+
359
+ ### ARIA usage (4.1.2)
360
+
361
+ **Prefer native elements:**
362
+ ```html
363
+ <!-- ❌ ARIA role on div -->
364
+ <div role="button" tabindex="0">Click me</div>
365
+
366
+ <!-- ✅ Native button -->
367
+ <button>Click me</button>
368
+
369
+ <!-- ❌ ARIA checkbox -->
370
+ <div role="checkbox" aria-checked="false">Option</div>
371
+
372
+ <!-- ✅ Native checkbox -->
373
+ <label><input type="checkbox"> Option</label>
374
+ ```
375
+
376
+ **When ARIA is needed,** use the correct roles and states. See the [ARIA tabs pattern](references/A11Y-PATTERNS.md#aria-tabs) for a complete tablist example.
377
+
378
+ ### Live regions (4.1.3)
379
+
380
+ Use `aria-live` regions to announce dynamic content changes without moving focus. See the [live regions pattern](references/A11Y-PATTERNS.md#live-regions-and-notifications) for markup and a `showNotification()` helper.
381
+
382
+ ---
383
+
384
+ ## Testing checklist
385
+
386
+ ### Automated testing
387
+ ```bash
388
+ # Lighthouse accessibility audit
389
+ npx lighthouse https://example.com --only-categories=accessibility
390
+
391
+ # axe-core
392
+ npm install @axe-core/cli -g
393
+ axe https://example.com
394
+ ```
395
+
396
+ ### Manual testing
397
+
398
+ - [ ] **Keyboard navigation:** Tab through entire page, use Enter/Space to activate
399
+ - [ ] **Screen reader:** Test with VoiceOver (Mac), NVDA (Windows), or TalkBack (Android)
400
+ - [ ] **Zoom:** Content usable at 200% zoom
401
+ - [ ] **High contrast:** Test with Windows High Contrast Mode
402
+ - [ ] **Reduced motion:** Test with `prefers-reduced-motion: reduce`
403
+ - [ ] **Focus order:** Logical and follows visual order
404
+ - [ ] **Target size:** Interactive elements meet 24×24px minimum
405
+
406
+ See the [screen reader commands reference](references/A11Y-PATTERNS.md#screen-reader-commands) for VoiceOver and NVDA shortcuts.
407
+
408
+ ---
409
+
410
+ ## Common issues by impact
411
+
412
+ ### Critical (fix immediately)
413
+ 1. Missing form labels
414
+ 2. Missing image alt text
415
+ 3. Insufficient color contrast
416
+ 4. Keyboard traps
417
+ 5. No focus indicators
418
+
419
+ ### Serious (fix before launch)
420
+ 1. Missing page language
421
+ 2. Missing heading structure
422
+ 3. Non-descriptive link text
423
+ 4. Auto-playing media
424
+ 5. Missing skip links
425
+
426
+ ### Moderate (fix soon)
427
+ 1. Missing ARIA labels on icons
428
+ 2. Inconsistent navigation
429
+ 3. Missing error identification
430
+ 4. Timing without controls
431
+ 5. Missing landmark regions
432
+
433
+ ## References
434
+
435
+ - [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/)
436
+ - [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
437
+ - [Deque axe Rules](https://dequeuniversity.com/rules/axe/)
438
+ - [Web Quality Audit](../web-quality-audit/SKILL.md)
439
+ - [WCAG criteria reference](references/WCAG.md)
440
+ - [Accessibility code patterns](references/A11Y-PATTERNS.md)
@@ -0,0 +1,233 @@
1
+ # Accessibility Code Patterns
2
+
3
+ Practical, copy-paste-ready patterns for common accessibility requirements. Each pattern is self-contained and linked from the main [SKILL.md](../SKILL.md).
4
+
5
+ ---
6
+
7
+ ## Modal focus trap
8
+
9
+ Trap keyboard focus inside a modal dialog so Tab/Shift+Tab cycle through its focusable elements and Escape closes it.
10
+
11
+ ```javascript
12
+ function openModal(modal) {
13
+ const focusableElements = modal.querySelectorAll(
14
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
15
+ );
16
+ const firstElement = focusableElements[0];
17
+ const lastElement = focusableElements[focusableElements.length - 1];
18
+
19
+ modal.addEventListener('keydown', (e) => {
20
+ if (e.key === 'Tab') {
21
+ if (e.shiftKey && document.activeElement === firstElement) {
22
+ e.preventDefault();
23
+ lastElement.focus();
24
+ } else if (!e.shiftKey && document.activeElement === lastElement) {
25
+ e.preventDefault();
26
+ firstElement.focus();
27
+ }
28
+ }
29
+ if (e.key === 'Escape') {
30
+ closeModal();
31
+ }
32
+ });
33
+
34
+ firstElement.focus();
35
+ }
36
+ ```
37
+
38
+ The native `<dialog>` element handles focus trapping automatically—prefer it when browser support allows.
39
+
40
+ ---
41
+
42
+ ## Skip link
43
+
44
+ Allows keyboard users to bypass repetitive navigation and jump straight to main content.
45
+
46
+ ```html
47
+ <body>
48
+ <a href="#main-content" class="skip-link">Skip to main content</a>
49
+ <header><!-- navigation --></header>
50
+ <main id="main-content" tabindex="-1">
51
+ <!-- main content -->
52
+ </main>
53
+ </body>
54
+ ```
55
+
56
+ ```css
57
+ .skip-link {
58
+ position: absolute;
59
+ top: -40px;
60
+ left: 0;
61
+ background: #000;
62
+ color: #fff;
63
+ padding: 8px 16px;
64
+ z-index: 100;
65
+ }
66
+
67
+ .skip-link:focus {
68
+ top: 0;
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Error handling
75
+
76
+ Announce errors to screen readers and focus the first invalid field on submit.
77
+
78
+ ```html
79
+ <form novalidate>
80
+ <div class="field" aria-live="polite">
81
+ <label for="email">Email</label>
82
+ <input type="email" id="email"
83
+ aria-invalid="true"
84
+ aria-describedby="email-error">
85
+ <p id="email-error" class="error" role="alert">
86
+ Please enter a valid email address (e.g., name@example.com)
87
+ </p>
88
+ </div>
89
+ </form>
90
+ ```
91
+
92
+ ```javascript
93
+ form.addEventListener('submit', (e) => {
94
+ const firstError = form.querySelector('[aria-invalid="true"]');
95
+ if (firstError) {
96
+ e.preventDefault();
97
+ firstError.focus();
98
+
99
+ const errorSummary = document.getElementById('error-summary');
100
+ errorSummary.textContent =
101
+ `${errors.length} errors found. Please fix them and try again.`;
102
+ errorSummary.focus();
103
+ }
104
+ });
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Form labels
110
+
111
+ Every input needs an associated label—either explicit (`for`/`id`) or implicit (wrapping `<label>`).
112
+
113
+ ```html
114
+ <!-- ❌ No label association -->
115
+ <input type="email" placeholder="Email">
116
+
117
+ <!-- ✅ Explicit label -->
118
+ <label for="email">Email address</label>
119
+ <input type="email" id="email" name="email"
120
+ autocomplete="email" required>
121
+
122
+ <!-- ✅ Implicit label -->
123
+ <label>
124
+ Email address
125
+ <input type="email" name="email" autocomplete="email" required>
126
+ </label>
127
+
128
+ <!-- ✅ With instructions -->
129
+ <label for="password">Password</label>
130
+ <input type="password" id="password"
131
+ aria-describedby="password-requirements">
132
+ <p id="password-requirements">
133
+ Must be at least 8 characters with one number.
134
+ </p>
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Dragging movements
140
+
141
+ Any action triggered by dragging must offer a single-pointer alternative (WCAG 2.5.7).
142
+
143
+ ```html
144
+ <!-- ❌ Drag-only reorder -->
145
+ <ul class="sortable-list" draggable="true">
146
+ <li>Item 1</li>
147
+ <li>Item 2</li>
148
+ </ul>
149
+
150
+ <!-- ✅ Drag + button alternatives -->
151
+ <ul class="sortable-list">
152
+ <li>
153
+ <span>Item 1</span>
154
+ <button aria-label="Move Item 1 up">↑</button>
155
+ <button aria-label="Move Item 1 down">↓</button>
156
+ </li>
157
+ <li>
158
+ <span>Item 2</span>
159
+ <button aria-label="Move Item 2 up">↑</button>
160
+ <button aria-label="Move Item 2 down">↓</button>
161
+ </li>
162
+ </ul>
163
+ ```
164
+
165
+ Also applies to sliders, map panning, colour pickers, and similar drag-based widgets—always provide an equivalent click/tap or keyboard path.
166
+
167
+ ---
168
+
169
+ ## ARIA tabs
170
+
171
+ Tabs require `role="tablist"`, `role="tab"`, and `role="tabpanel"` with proper `aria-selected`, `aria-controls`, and keyboard support.
172
+
173
+ ```html
174
+ <div role="tablist" aria-label="Product information">
175
+ <button role="tab" id="tab-1" aria-selected="true"
176
+ aria-controls="panel-1">Description</button>
177
+ <button role="tab" id="tab-2" aria-selected="false"
178
+ aria-controls="panel-2" tabindex="-1">Reviews</button>
179
+ </div>
180
+ <div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
181
+ <!-- Panel content -->
182
+ </div>
183
+ <div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
184
+ <!-- Panel content -->
185
+ </div>
186
+ ```
187
+
188
+ Arrow keys should move focus between tabs; the active tab receives `tabindex="0"` while inactive tabs use `tabindex="-1"`.
189
+
190
+ ---
191
+
192
+ ## Live regions and notifications
193
+
194
+ Use `aria-live` to announce dynamic content changes to screen readers without moving focus.
195
+
196
+ ```html
197
+ <!-- Status updates (polite — waits for pause in speech) -->
198
+ <div aria-live="polite" aria-atomic="true" class="status">
199
+ <!-- Content updates announced to screen readers -->
200
+ </div>
201
+
202
+ <!-- Urgent alerts (assertive — interrupts) -->
203
+ <div role="alert" aria-live="assertive">
204
+ <!-- Interrupts current announcement -->
205
+ </div>
206
+ ```
207
+
208
+ ```javascript
209
+ function showNotification(message, type = 'polite') {
210
+ const container = document.getElementById(`${type}-announcer`);
211
+ container.textContent = '';
212
+ requestAnimationFrame(() => {
213
+ container.textContent = message;
214
+ });
215
+ }
216
+ ```
217
+
218
+ Clear the container before writing to ensure the same message triggers a new announcement.
219
+
220
+ ---
221
+
222
+ ## Screen reader commands
223
+
224
+ Quick reference for the most common screen reader shortcuts.
225
+
226
+ | Action | VoiceOver (Mac) | NVDA (Windows) |
227
+ |--------|-----------------|----------------|
228
+ | Start/Stop | ⌘ + F5 | Ctrl + Alt + N |
229
+ | Next item | VO + → | ↓ |
230
+ | Previous item | VO + ← | ↑ |
231
+ | Activate | VO + Space | Enter |
232
+ | Headings list | VO + U, then arrows | H / Shift + H |
233
+ | Links list | VO + U | K / Shift + K |