@fpkit/acss 1.0.0-beta.1 โ†’ 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +32 -0
  2. package/docs/README.md +325 -0
  3. package/docs/guides/accessibility.md +764 -0
  4. package/docs/guides/architecture.md +705 -0
  5. package/docs/guides/composition.md +688 -0
  6. package/docs/guides/css-variables.md +522 -0
  7. package/docs/guides/storybook.md +828 -0
  8. package/docs/guides/testing.md +817 -0
  9. package/docs/testing/focus-indicator-testing.md +437 -0
  10. package/libs/components/buttons/button.css +1 -1
  11. package/libs/components/buttons/button.css.map +1 -1
  12. package/libs/components/buttons/button.min.css +2 -2
  13. package/libs/components/icons/icon.d.cts +32 -32
  14. package/libs/components/icons/icon.d.ts +32 -32
  15. package/libs/components/list/list.css +1 -1
  16. package/libs/components/list/list.min.css +1 -1
  17. package/libs/index.css +1 -1
  18. package/libs/index.css.map +1 -1
  19. package/package.json +4 -3
  20. package/src/components/README.mdx +1 -1
  21. package/src/components/buttons/button.scss +5 -0
  22. package/src/components/buttons/button.stories.tsx +8 -5
  23. package/src/components/cards/card.stories.tsx +1 -1
  24. package/src/components/details/details.stories.tsx +1 -1
  25. package/src/components/form/form.stories.tsx +1 -1
  26. package/src/components/form/input.stories.tsx +1 -1
  27. package/src/components/form/select.stories.tsx +1 -1
  28. package/src/components/heading/README.mdx +292 -0
  29. package/src/components/icons/icon.stories.tsx +1 -1
  30. package/src/components/list/list.scss +1 -1
  31. package/src/components/nav/nav.stories.tsx +1 -1
  32. package/src/components/ui.stories.tsx +53 -19
  33. package/src/docs/accessibility.mdx +484 -0
  34. package/src/docs/composition.mdx +549 -0
  35. package/src/docs/css-variables.mdx +380 -0
  36. package/src/docs/fpkit-developer.mdx +545 -0
  37. package/src/introduction.mdx +356 -0
  38. package/src/styles/buttons/button.css +4 -0
  39. package/src/styles/buttons/button.css.map +1 -1
  40. package/src/styles/index.css +9 -3
  41. package/src/styles/index.css.map +1 -1
  42. package/src/styles/list/list.css +1 -1
  43. package/src/styles/utilities/_disabled.scss +5 -4
@@ -0,0 +1,484 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Guides/Accessibility" />
4
+
5
+ # Accessibility Guide
6
+
7
+ Learn how to maintain **WCAG 2.1 Level AA** compliance when using and composing
8
+ @fpkit/acss components.
9
+
10
+ > **๐Ÿ“– Full Guide:** For comprehensive documentation, see
11
+ > [docs/guides/accessibility.md](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/accessibility.md)
12
+
13
+ ---
14
+
15
+ ## Quick Reference
16
+
17
+ fpkit components are **WCAG 2.1 Level AA compliant** by default. Your
18
+ responsibility is to maintain accessibility when:
19
+
20
+ - Composing custom components
21
+ - Customizing with CSS variables
22
+ - Adding custom interactions
23
+ - Creating forms and interactive elements
24
+
25
+ ---
26
+
27
+ ## Core Principles
28
+
29
+ ### 1. Semantic HTML First
30
+
31
+ fpkit components use appropriate semantic elements:
32
+
33
+ ```tsx
34
+ // โœ… Good - uses semantic button
35
+ <Button onClick={handleClick}>Click me</Button>
36
+ // Renders: <button type="button">Click me</button>
37
+
38
+ // โœ… Good - uses semantic link
39
+ <Button as="a" href="/page">Navigate</Button>
40
+ // Renders: <a href="/page">Navigate</a>
41
+
42
+ // โŒ Bad - div as button
43
+ <div onClick={handleClick}>Click me</div>
44
+ ```
45
+
46
+ ### 2. Keyboard Navigation
47
+
48
+ All fpkit interactive components support:
49
+
50
+ - **Tab**: Navigate between elements
51
+ - **Enter/Space**: Activate buttons and links
52
+ - **Escape**: Close modals and dialogs
53
+ - **Arrow keys**: Navigate menus and lists
54
+
55
+ **Test:** Try navigating your app using only the keyboard!
56
+
57
+ ### 3. Focus Management
58
+
59
+ fpkit provides built-in focus indicators:
60
+
61
+ ```css
62
+ /* Customize focus styles */
63
+ :root {
64
+ --btn-focus-outline: 2px solid #0066cc;
65
+ --btn-focus-outline-offset: 2px;
66
+ }
67
+ ```
68
+
69
+ ### 4. Screen Reader Support
70
+
71
+ fpkit includes proper ARIA attributes. Your job is to add context:
72
+
73
+ ```tsx
74
+ // โœ… Good - descriptive label
75
+ <Button aria-label="Close dialog">
76
+ <Icon name="close" />
77
+ </Button>
78
+
79
+ // โŒ Bad - no label
80
+ <Button>
81
+ <Icon name="close" />
82
+ </Button>
83
+ ```
84
+
85
+ ---
86
+
87
+ ## ARIA Attributes
88
+
89
+ ### Labels
90
+
91
+ ```tsx
92
+ // Icon-only button needs aria-label
93
+ <Button aria-label="Close dialog">
94
+ <Icon name="close" />
95
+ </Button>
96
+
97
+ // Button with visible text - no aria-label needed
98
+ <Button>
99
+ <Icon name="save" aria-hidden="true" />
100
+ Save
101
+ </Button>
102
+
103
+ // Group with aria-labelledby
104
+ <div role="group" aria-labelledby="filter-heading">
105
+ <h3 id="filter-heading">Filter Options</h3>
106
+ <Button>Apply</Button>
107
+ </div>
108
+
109
+ // Additional description
110
+ <Input
111
+ type="email"
112
+ aria-describedby="email-hint"
113
+ />
114
+ <div id="email-hint">We'll never share your email</div>
115
+ ```
116
+
117
+ ### States
118
+
119
+ ```tsx
120
+ // Expanded state (dropdowns)
121
+ <Button aria-expanded={isOpen} aria-controls="menu-list">
122
+ Menu
123
+ </Button>
124
+ <div id="menu-list" hidden={!isOpen}>
125
+ {/* Menu items */}
126
+ </div>
127
+
128
+ // Toggle state (toolbar)
129
+ <Button aria-pressed={isBold} onClick={toggleBold}>
130
+ <Icon name="bold" aria-hidden="true" />
131
+ Bold
132
+ </Button>
133
+
134
+ // Current page
135
+ <nav aria-label="Pagination">
136
+ <Button aria-current="page">1</Button>
137
+ <Button>2</Button>
138
+ </nav>
139
+ ```
140
+
141
+ ### Live Regions
142
+
143
+ ```tsx
144
+ // Polite announcement (non-urgent)
145
+ <div aria-live="polite" aria-atomic="true">
146
+ {statusMessage}
147
+ </div>
148
+
149
+ // Alert role (urgent messages)
150
+ <Alert variant="error" role="alert">
151
+ Form submission failed.
152
+ </Alert>
153
+
154
+ // Status role (progress updates)
155
+ <div role="status" aria-live="polite">
156
+ Saving... {progress}% complete
157
+ </div>
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Button Patterns
163
+
164
+ ### Why fpkit Uses aria-disabled
165
+
166
+ ```tsx
167
+ // fpkit pattern - stays in tab order
168
+ <Button disabled>Submit</Button>
169
+ // Renders: <button aria-disabled="true">Submit</button>
170
+
171
+ // Benefits:
172
+ // - Keyboard accessible
173
+ // - Can show tooltips
174
+ // - Users understand why disabled
175
+ ```
176
+
177
+ ### Button Types
178
+
179
+ ```tsx
180
+ // Inside forms - specify type
181
+ <form>
182
+ <Button type="submit">Save</Button>
183
+ <Button type="button">Cancel</Button>
184
+ </form>
185
+
186
+ // Outside forms - defaults to "button"
187
+ <Button onClick={handleAction}>Action</Button>
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Form Patterns
193
+
194
+ ### Field Labels
195
+
196
+ ```tsx
197
+ // โœ… Good - explicit association
198
+ <label htmlFor="email">Email Address</label>
199
+ <Input id="email" type="email" />
200
+
201
+ // โœ… Good - implicit association
202
+ <label>
203
+ Email Address
204
+ <Input type="email" />
205
+ </label>
206
+
207
+ // โŒ Bad - no association
208
+ <label>Email Address</label>
209
+ <Input type="email" />
210
+ ```
211
+
212
+ ### Error Messages
213
+
214
+ ```tsx
215
+ <label htmlFor="password">Password</label>
216
+ <Input
217
+ id="password"
218
+ type="password"
219
+ aria-describedby={hasError ? 'password-error' : undefined}
220
+ aria-invalid={hasError}
221
+ />
222
+ {hasError && (
223
+ <div id="password-error" role="alert">
224
+ Password must be at least 8 characters
225
+ </div>
226
+ )}
227
+ ```
228
+
229
+ ### Required Fields
230
+
231
+ ```tsx
232
+ <label htmlFor="name">
233
+ Name <span aria-label="required">*</span>
234
+ </label>
235
+ <Input
236
+ id="name"
237
+ type="text"
238
+ required
239
+ aria-required="true"
240
+ />
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Color Contrast
246
+
247
+ ### WCAG AA Requirements
248
+
249
+ - **Normal text** (< 18pt): **4.5:1** contrast ratio
250
+ - **Large text** (โ‰ฅ 18pt): **3:1** contrast ratio
251
+ - **UI components**: **3:1** contrast ratio
252
+
253
+ ### Testing
254
+
255
+ 1. **Browser DevTools** โ†’ Elements โ†’ Accessibility tab
256
+ 2. **Storybook a11y addon** โ†’ Check violations in Accessibility panel
257
+ 3. **[WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)**
258
+
259
+ ### Built-in Compliance
260
+
261
+ fpkit components meet requirements by default:
262
+
263
+ ```scss
264
+ // Example contrasts
265
+ --btn-primary-bg: #0066cc; // Blue
266
+ --btn-primary-color: #ffffff; // White (7.5:1 - exceeds AA)
267
+
268
+ --alert-error-bg: #f8d7da;
269
+ --alert-error-color: #721c24; // Dark red (9.2:1 - exceeds AA)
270
+ ```
271
+
272
+ **When customizing:** Always test contrast!
273
+
274
+ ---
275
+
276
+ ## Testing Accessibility
277
+
278
+ ### Storybook a11y Addon
279
+
280
+ Storybook includes automatic accessibility testing:
281
+
282
+ 1. Open any story in Storybook
283
+ 2. Click **Accessibility** tab
284
+ 3. Review violations, passes, and incomplete checks
285
+ 4. Fix issues before deploying
286
+
287
+ ### Automated Testing
288
+
289
+ ```bash
290
+ npm install -D jest-axe
291
+ ```
292
+
293
+ ```tsx
294
+ import { axe, toHaveNoViolations } from "jest-axe";
295
+
296
+ expect.extend(toHaveNoViolations);
297
+
298
+ it("should not have accessibility violations", async () => {
299
+ const { container } = render(<Button>Click me</Button>);
300
+ const results = await axe(container);
301
+ expect(results).toHaveNoViolations();
302
+ });
303
+ ```
304
+
305
+ ### Manual Testing Checklist
306
+
307
+ - [ ] **Keyboard Navigation** - Tab through all interactive elements
308
+ - [ ] **Screen Reader** - Test with VoiceOver (macOS), NVDA (Windows)
309
+ - [ ] **Focus Indicators** - Visible focus states (3:1 contrast ratio)
310
+ - [ ] **Accessible Names** - All interactive elements have labels
311
+ - [ ] **Color Contrast** - Text meets 4.5:1 for normal, 3:1 for large
312
+ - [ ] **Semantic Structure** - Proper heading hierarchy and landmarks
313
+
314
+ ---
315
+
316
+ ## Interactive Elements
317
+
318
+ ### Making Non-Button Elements Clickable
319
+
320
+ ```tsx
321
+ import { Card } from '@fpkit/acss'
322
+
323
+ // โŒ Bad - div with onClick
324
+ <Card onClick={handleClick}>Clickable</Card>
325
+
326
+ // โœ… Better - add role and keyboard support
327
+ <Card
328
+ as="article"
329
+ role="button"
330
+ tabIndex={0}
331
+ onClick={handleClick}
332
+ onKeyDown={(e) => {
333
+ if (e.key === 'Enter' || e.key === ' ') {
334
+ e.preventDefault()
335
+ handleClick(e)
336
+ }
337
+ }}
338
+ aria-label="View article details"
339
+ >
340
+ {/* Card content */}
341
+ </Card>
342
+
343
+ // โœ… Best - use semantic element
344
+ <Card as="a" href="/article/123">
345
+ {/* Card content */}
346
+ </Card>
347
+ ```
348
+
349
+ **Requirements for custom interactive elements:**
350
+
351
+ - `role="button"` - Announces as interactive
352
+ - `tabIndex={0}` - Makes keyboard focusable
353
+ - `onClick` - Mouse interaction
354
+ - `onKeyDown` - Keyboard activation (Enter/Space)
355
+ - `aria-label` - Descriptive label
356
+
357
+ ---
358
+
359
+ ## Common Mistakes
360
+
361
+ ### โŒ Don't
362
+
363
+ - Use `div` or `span` as buttons without proper ARIA
364
+ - Remove focus outlines without alternatives
365
+ - Use `placeholder` as a label replacement
366
+ - Use color alone to convey information
367
+ - Nest interactive elements (`<button>` inside `<a>`)
368
+ - Use positive `tabindex` values (> 0)
369
+ - Auto-play audio/video without controls
370
+
371
+ ### โœ… Do
372
+
373
+ - Use semantic HTML elements
374
+ - Provide visible focus indicators
375
+ - Include proper labels for all form controls
376
+ - Use multiple cues (color + icon, color + text)
377
+ - Ensure modals trap focus
378
+ - Use `aria-live="polite"` for non-critical updates
379
+ - Test with keyboard and screen readers
380
+
381
+ ---
382
+
383
+ ## Composing Accessible Components
384
+
385
+ When composing fpkit components, maintain accessibility:
386
+
387
+ ```tsx
388
+ import { Button, Badge } from "@fpkit/acss";
389
+
390
+ // โœ… Good - maintains accessibility
391
+ export const NotificationButton = ({ count, onClick }) => {
392
+ return (
393
+ <Button onClick={onClick} aria-label={`Notifications (${count} unread)`}>
394
+ <Icon name="bell" aria-hidden="true" />
395
+ {count > 0 && <Badge aria-hidden="true">{count}</Badge>}
396
+ </Button>
397
+ );
398
+ };
399
+ ```
400
+
401
+ **Why this works:**
402
+
403
+ - Button is keyboard accessible (inherits from fpkit)
404
+ - `aria-label` provides context for screen readers
405
+ - Visual elements hidden with `aria-hidden`
406
+ - Count announced via `aria-label`
407
+
408
+ ---
409
+
410
+ ## WCAG 2.1 Level AA Checklist
411
+
412
+ ### Perceivable
413
+
414
+ - [ ] Text alternatives for non-text content
415
+ - [ ] Content presentable in different ways
416
+ - [ ] Sufficient color contrast (4.5:1 normal, 3:1 large)
417
+ - [ ] Text resizable up to 200%
418
+
419
+ ### Operable
420
+
421
+ - [ ] All functionality keyboard accessible
422
+ - [ ] No keyboard traps
423
+ - [ ] Users have enough time to interact
424
+ - [ ] No content flashes > 3 times per second
425
+ - [ ] Clear page titles and headings
426
+ - [ ] Visible focus indicators
427
+ - [ ] Multiple navigation methods
428
+
429
+ ### Understandable
430
+
431
+ - [ ] Language programmatically determined
432
+ - [ ] Labels for user input
433
+ - [ ] Clear error messages
434
+ - [ ] Consistent navigation
435
+ - [ ] Predictable behavior
436
+
437
+ ### Robust
438
+
439
+ - [ ] Valid HTML (no duplicate IDs)
440
+ - [ ] Correct ARIA usage
441
+ - [ ] Compatible with assistive technologies
442
+ - [ ] Status messages announced
443
+
444
+ ---
445
+
446
+ ## Tools & Resources
447
+
448
+ ### Testing Tools
449
+
450
+ | Tool | Type | Use Case |
451
+ | ---------------------------------------------------------------- | ----------------- | ----------------------------- |
452
+ | [axe DevTools](https://www.deque.com/axe/devtools/) | Browser Extension | Real-time violation detection |
453
+ | [WAVE](https://wave.webaim.org/extension/) | Browser Extension | Visual feedback |
454
+ | [Lighthouse](https://developers.google.com/web/tools/lighthouse) | Built-in Chrome | Comprehensive audit |
455
+ | [jest-axe](https://github.com/nickcolley/jest-axe) | Testing Library | Automated unit testing |
456
+ | Storybook a11y addon | Storybook | Interactive testing |
457
+
458
+ ### Guidelines
459
+
460
+ - [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
461
+ - [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
462
+ - [WebAIM Articles](https://webaim.org/articles/)
463
+
464
+ ---
465
+
466
+ ## Additional Resources
467
+
468
+ - **๐Ÿ“–
469
+ [Full Accessibility Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/accessibility.md)** -
470
+ Comprehensive WCAG patterns
471
+ - **๐Ÿ“˜
472
+ [Composition Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/composition.md)** -
473
+ Building accessible compositions
474
+ - **๐Ÿงช
475
+ [Testing Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/testing.md)** -
476
+ Accessibility testing strategies
477
+
478
+ ---
479
+
480
+ **Remember:** Accessibility is not optional. fpkit provides accessible
481
+ components by default - your job is to maintain that accessibility when
482
+ composing and customizing them.
483
+
484
+ **Use the Storybook a11y addon** on every story to catch violations early!