picasso-skill 1.2.0 → 1.3.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.
@@ -0,0 +1,513 @@
1
+ # UX Writing Reference
2
+
3
+ Rules and patterns for writing UI text. Every string a user reads is a design decision.
4
+
5
+ ---
6
+
7
+ ## 1. Button Labels
8
+
9
+ Never use generic labels. Every button tells the user exactly what will happen.
10
+
11
+ ### Banned Labels
12
+
13
+ | Never Use | Why |
14
+ |-----------|-----|
15
+ | OK | Tells user nothing about the action |
16
+ | Submit | Vague, form-era holdover |
17
+ | Yes / No | Forces user to re-read the question |
18
+ | Click here | Accessibility failure, no meaning |
19
+ | Cancel (alone) | Ambiguous without paired action context |
20
+
21
+ ### Pattern: Verb + Object
22
+
23
+ ```
24
+ Save changes
25
+ Create account
26
+ Delete 3 items
27
+ Export as PDF
28
+ Add to cart
29
+ Send invitation
30
+ Publish post
31
+ Archive conversation
32
+ ```
33
+
34
+ ### Destructive Actions: Name the Destruction + Show Count
35
+
36
+ ```
37
+ // BAD
38
+ Are you sure? [Yes] [No]
39
+
40
+ // GOOD
41
+ Delete 14 messages permanently?
42
+ This cannot be undone.
43
+ [Keep messages] [Delete 14 messages]
44
+ ```
45
+
46
+ ```
47
+ // BAD
48
+ Remove items? [OK] [Cancel]
49
+
50
+ // GOOD
51
+ Remove 3 items from your cart?
52
+ [Keep items] [Remove 3 items]
53
+ ```
54
+
55
+ ### Paired Button Rules
56
+
57
+ | Scenario | Primary | Secondary |
58
+ |----------|---------|-----------|
59
+ | Save flow | Save changes | Discard |
60
+ | Creation | Create project | Cancel |
61
+ | Destructive | Keep items | Delete 5 items |
62
+ | Confirmation | Send invitation | Go back |
63
+ | Upload | Upload file | Choose different file |
64
+
65
+ The safe/non-destructive action is always visually more prominent (filled button). The destructive action is secondary (outlined or text-only).
66
+
67
+ ---
68
+
69
+ ## 2. Error Messages
70
+
71
+ ### Formula: What Happened + Why + How to Fix
72
+
73
+ Every error message has three parts. Never skip "how to fix."
74
+
75
+ ### Templates
76
+
77
+ **Format error:**
78
+ ```
79
+ Title: Invalid email address
80
+ Body: Email addresses need an @ symbol and a domain (like name@example.com).
81
+ Action: [Fix email address]
82
+ ```
83
+
84
+ **Missing required field:**
85
+ ```
86
+ Title: Card number is required
87
+ Body: Enter your 16-digit card number to complete checkout.
88
+ // Inline: highlight the field, place message directly below it
89
+ ```
90
+
91
+ **Permission denied:**
92
+ ```
93
+ Title: You don't have access to this project
94
+ Body: Only project admins can change billing settings. Ask [Admin Name] to update your role or make this change for you.
95
+ Action: [Request access] [Go back]
96
+ ```
97
+
98
+ **Network error:**
99
+ ```
100
+ Title: Can't connect right now
101
+ Body: Check your internet connection and try again. Your draft has been saved locally.
102
+ Action: [Try again]
103
+ ```
104
+
105
+ **Server error (5xx):**
106
+ ```
107
+ Title: Something went wrong on our end
108
+ Body: We're looking into it. Your data is safe. Try again in a few minutes, or contact support if this keeps happening.
109
+ Action: [Try again] [Contact support]
110
+ ```
111
+
112
+ **Rate limit:**
113
+ ```
114
+ Title: Too many requests
115
+ Body: You've hit the rate limit. Try again in [X] seconds.
116
+ Action: [auto-countdown button: "Try again in 28s"]
117
+ ```
118
+
119
+ ### Rules
120
+
121
+ - Never blame the user. "Invalid input" becomes "This field needs a number between 1 and 100."
122
+ - Never use error codes alone. "Error 403" means nothing. Always pair with human language.
123
+ - Never use "Oops!" or humor in error states. The user is blocked; respect that.
124
+ - Place errors inline next to the field, not in a modal or toast that disappears.
125
+ - Use red (#DC2626 or equivalent) for error borders/icons, not for the entire message text.
126
+
127
+ ---
128
+
129
+ ## 3. Empty States
130
+
131
+ ### Formula: Acknowledge + Explain Value + Action CTA + Optional Help
132
+
133
+ ```
134
+ // Project list empty state
135
+
136
+ [illustration or icon]
137
+
138
+ No projects yet
139
+ Projects help you organize work, track progress, and collaborate with your team.
140
+
141
+ [Create your first project]
142
+
143
+ Need help getting started? Read the quick-start guide.
144
+ ```
145
+
146
+ ```
147
+ // Search with no results
148
+
149
+ No results for "exmple"
150
+ Did you mean "example"?
151
+
152
+ Try different keywords, remove filters, or [browse all items].
153
+ ```
154
+
155
+ ```
156
+ // Filtered view empty
157
+
158
+ No completed tasks this week
159
+ Completed tasks will appear here as your team finishes work.
160
+
161
+ [View all tasks]
162
+ ```
163
+
164
+ ### Rules
165
+
166
+ - Keep the acknowledgment to one short line. Don't over-explain emptiness.
167
+ - The value proposition should answer "why would I want things here?"
168
+ - The CTA button uses the same verb+object pattern as all buttons.
169
+ - Help links open in context (panel, tooltip, or new tab), never navigate away from the empty state.
170
+ - Never show a completely blank screen. Even loading-to-empty transitions need a state.
171
+
172
+ ---
173
+
174
+ ## 4. Voice vs Tone
175
+
176
+ **Voice** is the personality. It never changes. Define it once.
177
+
178
+ | Voice Attribute | Means | Does Not Mean |
179
+ |----------------|-------|---------------|
180
+ | Clear | Short sentences, common words | Dumbed down or robotic |
181
+ | Confident | Direct statements, no hedging | Arrogant or dismissive |
182
+ | Helpful | Guides next step, anticipates needs | Condescending or hand-holding |
183
+ | Human | Natural phrasing, contractions OK | Slang, jokes, or emoji in UI text |
184
+
185
+ **Tone** adapts to the moment.
186
+
187
+ | Moment | Tone | Example |
188
+ |--------|------|---------|
189
+ | Success | Warm, encouraging | "Your account is ready. Let's set up your first project." |
190
+ | Error | Calm, direct | "That file is too large. The maximum size is 25 MB." |
191
+ | Destructive confirmation | Serious, precise | "This will permanently delete 14 messages. This cannot be undone." |
192
+ | Onboarding | Friendly, guiding | "Welcome. We'll walk you through the basics in about 2 minutes." |
193
+ | Empty state | Neutral, inviting | "No notifications yet. You'll see updates from your team here." |
194
+
195
+ ### Hard Rule
196
+
197
+ Never use humor in error states, destructive actions, or security messages. A user who just lost data does not want a witty quip.
198
+
199
+ ---
200
+
201
+ ## 5. Accessibility Writing
202
+
203
+ ### Link Text
204
+
205
+ Links must make sense when read alone (screen readers navigate by link list).
206
+
207
+ ```
208
+ // BAD
209
+ To learn more about pricing, click here.
210
+
211
+ // GOOD
212
+ View our pricing plans.
213
+
214
+ // BAD
215
+ Read more
216
+
217
+ // GOOD
218
+ Read the full accessibility guidelines
219
+ ```
220
+
221
+ ### Alt Text
222
+
223
+ Describe the information the image conveys, not the image itself.
224
+
225
+ ```
226
+ // BAD
227
+ alt="photo"
228
+ alt="image of a chart"
229
+ alt="" (unless purely decorative)
230
+
231
+ // GOOD
232
+ alt="Revenue grew 34% from Q1 to Q4 2025"
233
+ alt="Team photo: 12 people standing in front of the office"
234
+ alt="" (decorative divider, background pattern)
235
+ ```
236
+
237
+ Rule: If the image were deleted, what information would be lost? That is your alt text.
238
+
239
+ ### Icon Buttons
240
+
241
+ Every icon-only button needs an `aria-label`.
242
+
243
+ ```html
244
+ <!-- BAD -->
245
+ <button><TrashIcon /></button>
246
+
247
+ <!-- GOOD -->
248
+ <button aria-label="Delete message"><TrashIcon /></button>
249
+
250
+ <!-- With tooltip for sighted users too -->
251
+ <button aria-label="Delete message" title="Delete message">
252
+ <TrashIcon />
253
+ </button>
254
+ ```
255
+
256
+ ### Form Labels
257
+
258
+ Every input has a visible `<label>`. Placeholder text is not a label.
259
+
260
+ ```html
261
+ <!-- BAD -->
262
+ <input placeholder="Enter your email" />
263
+
264
+ <!-- GOOD -->
265
+ <label for="email">Email address</label>
266
+ <input id="email" placeholder="name@example.com" />
267
+ ```
268
+
269
+ ---
270
+
271
+ ## 6. Translation Planning
272
+
273
+ English is compact. Other languages expand. Design with this in mind.
274
+
275
+ ### Expansion Percentages
276
+
277
+ | Language | Expansion | 10-char English becomes |
278
+ |----------|-----------|------------------------|
279
+ | German | +30% | ~13 characters |
280
+ | French | +20% | ~12 characters |
281
+ | Finnish | +30-40% | ~13-14 characters |
282
+ | Italian | +15% | ~11-12 characters |
283
+ | Spanish | +15-20% | ~12 characters |
284
+ | Portuguese | +20-25% | ~12-13 characters |
285
+ | Chinese | -30% | ~7 characters |
286
+ | Japanese | -20-30% | ~7-8 characters |
287
+ | Korean | -10-20% | ~8-9 characters |
288
+ | Arabic | +25% (RTL) | ~12-13 characters |
289
+
290
+ ### Design Rules
291
+
292
+ - Buttons: Allow at least 30% extra width. Never set fixed-width buttons for text.
293
+ - Navigation: Horizontal nav bars that barely fit in English will break in German. Test with pseudolocalization.
294
+ - Truncation: If you must truncate, truncate with `...` and provide the full text on hover/focus via `title` attribute.
295
+ - Icons + text: Always pair icons with text labels for translatability. Icon-only works only for universally understood symbols (close X, hamburger menu).
296
+ - Strings: Never concatenate strings. Use full-sentence templates with placeholders: `"Showing {count} of {total} results"` not `"Showing " + count + " of " + total + " results"`.
297
+ - Pluralization: Use ICU MessageFormat or equivalent. Never assume `count === 1` is the only singular rule (Arabic has 6 plural forms).
298
+
299
+ ---
300
+
301
+ ## 7. Terminology Consistency
302
+
303
+ Pick one term. Use it everywhere. Document it here.
304
+
305
+ | Use This | Not This |
306
+ |----------|----------|
307
+ | Delete | Remove, Trash, Erase, Destroy |
308
+ | Settings | Preferences, Options, Configuration |
309
+ | Sign in | Log in, Login, Sign on |
310
+ | Sign out | Log out, Logout, Sign off |
311
+ | Sign up | Register, Create account, Join |
312
+ | Search | Find, Look up, Query |
313
+ | Edit | Modify, Change, Update (in UI labels) |
314
+ | Save | Apply (for saving data, not filter application) |
315
+ | Profile | Account (for user identity page) |
316
+ | Workspace | Organization, Team, Company |
317
+ | Member | User (in team context) |
318
+ | Notification | Alert (reserve "alert" for system-level) |
319
+
320
+ ### Enforcement
321
+
322
+ - Create a glossary file in the project. Lint against it.
323
+ - When a new term is introduced, add it to the glossary before shipping.
324
+ - Search the entire codebase when renaming. Partial renames are worse than inconsistency.
325
+
326
+ ---
327
+
328
+ ## 8. Loading State Copy
329
+
330
+ Never show "Loading..." for more than 2 seconds without context.
331
+
332
+ ### Progressive Messages
333
+
334
+ ```
335
+ // Immediate (0-2s)
336
+ [spinner]
337
+
338
+ // Short wait (2-5s)
339
+ Loading your dashboard...
340
+
341
+ // Medium wait (5-15s)
342
+ Crunching the numbers. This usually takes a few seconds.
343
+
344
+ // Long wait (15s+)
345
+ Still working on it. Large datasets take a bit longer.
346
+
347
+ // Very long (30s+)
348
+ This is taking longer than usual. You can wait or [try again].
349
+ ```
350
+
351
+ ### Skeleton Screens Over Spinners
352
+
353
+ Use skeleton screens (gray placeholder shapes) for known layouts. Use spinners only for unknown or variable layouts.
354
+
355
+ ```
356
+ // Known layout: use skeleton
357
+ [====== skeleton heading ======]
358
+ [== skeleton line 1 ===========]
359
+ [== skeleton line 2 =======]
360
+ [== skeleton line 3 ==============]
361
+
362
+ // Unknown layout: use spinner with context
363
+ [spinner] Searching 12,000 records...
364
+ ```
365
+
366
+ ### Progress Indicators
367
+
368
+ For operations with known progress, show a percentage or step count.
369
+
370
+ ```
371
+ Uploading 3 of 7 files (42%)
372
+ [=========> ]
373
+
374
+ Importing contacts... Step 2 of 4: Validating email addresses
375
+ ```
376
+
377
+ ---
378
+
379
+ ## 9. Confirmation Dialogs
380
+
381
+ ### Structure
382
+
383
+ ```
384
+ Title: State what will happen (verb + object)
385
+ Body: Consequences in plain language. Irreversibility if applicable.
386
+ Actions: [Safe option] [Destructive option]
387
+ ```
388
+
389
+ ### Non-Destructive Confirmation
390
+
391
+ ```
392
+ Title: Publish this article?
393
+ Body: It will be visible to everyone in your workspace immediately.
394
+ Actions: [Go back] [Publish article]
395
+ ```
396
+
397
+ ### Destructive Confirmation
398
+
399
+ ```
400
+ Title: Delete "Q4 Report" and 3 attachments?
401
+ Body: This will permanently delete the document and its attachments.
402
+ This cannot be undone.
403
+ Actions: [Keep document] [Delete document and attachments]
404
+ ```
405
+
406
+ ### High-Stakes Destructive (Type-to-Confirm)
407
+
408
+ ```
409
+ Title: Delete workspace "Acme Corp"?
410
+ Body: This will permanently delete the workspace, all 47 projects,
411
+ and all member data. This cannot be undone.
412
+
413
+ Type "Acme Corp" to confirm:
414
+ [ ]
415
+
416
+ Actions: [Cancel] [Delete workspace] (disabled until typed)
417
+ ```
418
+
419
+ ### Rules
420
+
421
+ - The safe action (cancel/keep/go back) is the visually prominent button (filled, primary color).
422
+ - The destructive action is secondary (outlined, red text or red outline).
423
+ - Never pre-select the destructive option.
424
+ - For keyboard users, focus lands on the safe action by default.
425
+ - Escape key always triggers the safe action.
426
+
427
+ ---
428
+
429
+ ## 10. Microcopy
430
+
431
+ ### Placeholder vs Label
432
+
433
+ Placeholders vanish on input. Labels stay visible. Use both.
434
+
435
+ ```html
436
+ <!-- BAD: placeholder as only label -->
437
+ <input placeholder="Email" />
438
+
439
+ <!-- GOOD: visible label + helpful placeholder -->
440
+ <label for="email">Email address</label>
441
+ <input id="email" placeholder="name@example.com" />
442
+ ```
443
+
444
+ ### Helper Text
445
+
446
+ Place below the input. Use for format hints, constraints, or context.
447
+
448
+ ```
449
+ Password
450
+ [ ]
451
+ Must be at least 8 characters with one number and one symbol.
452
+
453
+ Phone number
454
+ [ ]
455
+ We'll only use this for two-factor authentication.
456
+ ```
457
+
458
+ ### Success Messages with Next Steps
459
+
460
+ Never dead-end a user after success. Always tell them what comes next.
461
+
462
+ ```
463
+ // BAD
464
+ Success!
465
+
466
+ // GOOD
467
+ Account created. Check your email for a verification link.
468
+
469
+ // BAD
470
+ Payment received.
471
+
472
+ // GOOD
473
+ Payment received. Your order #4821 will ship within 2 business days.
474
+ You'll get a tracking email when it does. [View order details]
475
+
476
+ // BAD
477
+ File uploaded.
478
+
479
+ // GOOD
480
+ "Q4-Report.pdf" uploaded. [Share with team] [Upload another]
481
+ ```
482
+
483
+ ### Character Counts
484
+
485
+ Show remaining characters, not total limit, and only when the user is close to the limit.
486
+
487
+ ```
488
+ // Show nothing until 80% of limit reached
489
+ // At 80%+:
490
+ 23 characters remaining
491
+
492
+ // At limit:
493
+ 0 characters remaining (input blocked or visual warning)
494
+
495
+ // Over limit (if allowed):
496
+ 12 characters over limit (red, with explanation of what happens)
497
+ ```
498
+
499
+ ### Toggle Labels
500
+
501
+ Label the current state, not the action. Or label both sides.
502
+
503
+ ```
504
+ // BAD (ambiguous: is it on or will clicking turn it on?)
505
+ [toggle] Notifications
506
+
507
+ // GOOD: label both states
508
+ Notifications: Off [toggle] On
509
+
510
+ // GOOD: describe what toggle does
511
+ [toggle] Receive email notifications
512
+ Currently: Off
513
+ ```
@@ -1,172 +0,0 @@
1
- # Accessibility Reference
2
-
3
- ## Table of Contents
4
- 1. Semantic HTML
5
- 2. ARIA Patterns
6
- 3. Keyboard Navigation
7
- 4. Screen Reader Testing
8
- 5. Color and Contrast
9
- 6. Motion and Vestibular
10
- 7. Forms
11
- 8. Images and Media
12
- 9. Testing Tools
13
- 10. Common Mistakes
14
-
15
- ---
16
-
17
- ## 1. Semantic HTML
18
-
19
- Use the correct HTML element for its purpose. A `<button>` is always better than a `<div onClick>`. Semantic elements provide free behavior: focus, activation, role announcement, and keyboard interaction.
20
-
21
- ### Landmark Regions
22
- Every page needs landmarks (`<header>`, `<nav>`, `<main>`, `<aside>`, `<footer>`). Screen readers let users jump between them. Label multiples: `<nav aria-label="Primary">`.
23
-
24
- ### Document Outline
25
- Heading levels (`h1`-`h6`) in order. Never skip levels. One `h1` per page.
26
-
27
- ### Element Selection
28
- - Action: `<button>`. Navigation: `<a href>`. Lists: `<ul>`/`<ol>`. Data: `<table>` with `<th>`.
29
- - Toggle: `<button aria-pressed>` or `<input type="checkbox">`. Expandable: `<details>`/`<summary>`.
30
-
31
- ---
32
-
33
- ## 2. ARIA Patterns
34
-
35
- Do not use ARIA if native HTML achieves the same result. ARIA adds semantics but not behavior.
36
-
37
- ### Common Widgets
38
- **Tabs**: `role="tablist"` container with `role="tab"` buttons. Arrow keys move between tabs, Tab moves into `role="tabpanel"`. Manage `aria-selected` and `hidden`.
39
-
40
- **Dialog**: `role="dialog"` with `aria-modal="true"` and `aria-labelledby` pointing to the heading. Trap focus, close on Escape.
41
-
42
- ### Live Regions
43
- Inject content into these containers; do not add the role after content exists.
44
- ```html
45
- <div aria-live="polite" aria-atomic="true">3 items in cart</div>
46
- <div role="alert">Payment failed.</div> <!-- assertive, interrupts -->
47
- <div role="status">File uploaded.</div> <!-- polite, waits -->
48
- ```
49
-
50
- ---
51
-
52
- ## 3. Keyboard Navigation
53
-
54
- ### Focus Indicators
55
- Never remove outlines without replacement. Use `:focus-visible` for keyboard-only rings:
56
- ```css
57
- :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
58
- ```
59
-
60
- ### Tab Order
61
- DOM order determines tab order. Never use positive `tabindex`. Use `tabindex="-1"` for programmatic focus only.
62
-
63
- ### Roving Tabindex
64
- For composite widgets (toolbars, tab lists), one item is in the tab order at a time. Arrow keys move focus. Set `tabindex="0"` on the active item, `tabindex="-1"` on siblings, and call `.focus()` on arrow key press.
65
-
66
- ### Focus Trapping for Modals
67
- On open: focus first focusable element. Tab/Shift+Tab cycles within. Escape closes. On close: return focus to trigger. Use `inert` on background content to prevent interaction outside the modal.
68
-
69
- ---
70
-
71
- ## 4. Screen Reader Testing
72
-
73
- ### VoiceOver (macOS)
74
- Toggle: Cmd+F5. Navigate: VO (Ctrl+Option) + arrows. Rotor: VO+U. Test with Safari.
75
-
76
- ### NVDA (Windows)
77
- Toggle: Ctrl+Alt+N. Elements list: NVDA+F7. Read all: NVDA+Down. Test with Firefox.
78
-
79
- ### Methodology
80
- 1. Navigate with SR only. Verify elements announce role, name, state.
81
- 2. Verify dynamic changes (toasts, errors) are announced via live regions.
82
- 3. Verify modals trap focus and announce title. Verify custom widget keyboard patterns.
83
-
84
- ### Gotchas
85
- - `aria-hidden="true"` on a parent hides all children regardless of child attributes.
86
- - Dynamic content is not announced unless in a live region or focus is moved to it.
87
-
88
- ---
89
-
90
- ## 5. Color and Contrast
91
-
92
- ### WCAG 2.2 Requirements
93
- Normal text: 4.5:1 AA, 7:1 AAA. Large text (>=24px / >=18.66px bold): 3:1 AA, 4.5:1 AAA. UI components and focus indicators: 3:1 AA. APCA provides a more perceptually accurate model and is expected in WCAG 3.0.
94
-
95
- ### Color Blindness
96
- 8% of men have color vision deficiency. Never rely on color alone. Pair red/green with icons. Use patterns in charts alongside color. See `color-and-contrast.md` for deeper guidance.
97
-
98
- ---
99
-
100
- ## 6. Motion and Vestibular
101
-
102
- ### prefers-reduced-motion
103
- Remove translation, rotation, and scaling. Opacity fades remain acceptable.
104
- ```css
105
- @media (prefers-reduced-motion: reduce) {
106
- *, *::before, *::after {
107
- animation-duration: 0.01ms !important;
108
- transition-duration: 0.01ms !important;
109
- scroll-behavior: auto !important;
110
- }
111
- }
112
- ```
113
-
114
- ### prefers-contrast
115
- For `(prefers-contrast: more)`: bolder borders (2px+), higher text contrast, remove translucent/glass surfaces.
116
-
117
- ### What to Avoid
118
- - Flashing faster than 3/second (WCAG 2.3.1), auto-playing large animations
119
- - Parallax without reduced-motion fallback, infinite animations without pause
120
-
121
- ---
122
-
123
- ## 7. Forms
124
-
125
- Every input needs a programmatic `<label>`. Placeholders are not labels. Mark required fields with `required` and `aria-required="true"`.
126
-
127
- For errors, use `aria-invalid="true"` and `aria-describedby` pointing to the error message:
128
- ```html
129
- <input id="email" aria-invalid="true" aria-describedby="email-err" />
130
- <p id="email-err" role="alert">Enter a valid email address.</p>
131
- ```
132
- Validate on blur, not on keystroke. For multiple errors, move focus to an error summary with links to each invalid field.
133
-
134
- ---
135
-
136
- ## 8. Images and Media
137
-
138
- ### Alt Text
139
- - **Informative**: Describe content and purpose. **Decorative**: `alt=""` (empty, not absent).
140
- - **Functional** (in buttons/links): Describe the action, not the image. **Complex**: Short alt + `aria-describedby` or `<figcaption>`.
141
-
142
- ### Video and Audio
143
- Captions for all video (WCAG 1.2.2). Audio descriptions for visual-only content (WCAG 1.2.5). Never auto-play audio. Media players must be keyboard-operable.
144
-
145
- ---
146
-
147
- ## 9. Testing Tools
148
-
149
- ### Automated
150
- - **axe-core**: Industry standard. Browser extension, CLI, or CI via `@axe-core/playwright`. Catches ~30-40% of issues.
151
- - **Lighthouse**: Chrome DevTools, uses axe-core. **pa11y**: CLI for CI, supports WCAG 2.2 AA/AAA.
152
-
153
- ### Manual Checklist
154
- 1. Tab through entire page. Logical order, every control reachable.
155
- 2. Activate controls with Enter and Space.
156
- 3. Modals: focus trapped, Escape closes, focus returns.
157
- 4. 400% zoom: no horizontal scroll (WCAG 1.4.10).
158
- 5. Screen reader: correct roles, names, and states.
159
- 6. Images have appropriate alt text. Forms have labels and announced errors.
160
-
161
- ---
162
-
163
- ## 10. Common Mistakes
164
-
165
- - `<div>`/`<span>` for clickable elements instead of `<button>`/`<a>` (loses keyboard and semantics)
166
- - `outline: none` without a visible replacement focus indicator
167
- - Positive `tabindex` values (chaotic tab ordering)
168
- - `aria-hidden="true"` on focusable elements (reachable but invisible to AT)
169
- - `role="button"` on a `<div>` without `tabindex="0"` and Enter/Space handlers
170
- - Missing `lang` attribute on `<html>` (screen readers guess language wrong)
171
- - `aria-live` on a region updating every second (overwhelms SR users)
172
- - Wrapping entire cards in `<a>` (SR reads all card text as the link name)