@fpkit/acss 0.5.13 → 0.6.1
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/libs/{chunk-PQ2K3BM6.cjs → chunk-2NRIP6RB.cjs} +3 -3
- package/libs/chunk-33PNJ4LO.cjs +15 -0
- package/libs/chunk-33PNJ4LO.cjs.map +1 -0
- package/libs/chunk-4BZKFPEC.cjs +17 -0
- package/libs/chunk-4BZKFPEC.cjs.map +1 -0
- package/libs/{chunk-772NRB75.js → chunk-5QD3DWFI.js} +2 -2
- package/libs/chunk-6SAHIYCZ.js +7 -0
- package/libs/chunk-6SAHIYCZ.js.map +1 -0
- package/libs/{chunk-3MKLDCKQ.cjs → chunk-6WTC4JXH.cjs} +3 -3
- package/libs/chunk-75QHTLFO.js +7 -0
- package/libs/chunk-75QHTLFO.js.map +1 -0
- package/libs/{chunk-ZANSFMTD.js → chunk-7XPFW7CB.js} +3 -3
- package/libs/chunk-BFK62VX5.js +5 -0
- package/libs/chunk-BFK62VX5.js.map +1 -0
- package/libs/{chunk-ROZI23GS.cjs → chunk-DKTHCQ5P.cjs} +4 -4
- package/libs/chunk-E2AJURUW.cjs +13 -0
- package/libs/chunk-E2AJURUW.cjs.map +1 -0
- package/libs/{chunk-L75OQKEI.cjs → chunk-ENTCUJ3A.cjs} +3 -3
- package/libs/chunk-ENTCUJ3A.cjs.map +1 -0
- package/libs/chunk-F5EYMVQM.js +10 -0
- package/libs/chunk-F5EYMVQM.js.map +1 -0
- package/libs/chunk-FVROL3V5.js +9 -0
- package/libs/chunk-FVROL3V5.js.map +1 -0
- package/libs/chunk-GT77BX4L.cjs +17 -0
- package/libs/chunk-GT77BX4L.cjs.map +1 -0
- package/libs/chunk-GUJSMQ3V.cjs +16 -0
- package/libs/chunk-GUJSMQ3V.cjs.map +1 -0
- package/libs/chunk-HHLNOC5T.js +7 -0
- package/libs/chunk-HHLNOC5T.js.map +1 -0
- package/libs/chunk-HRRHPLER.js +8 -0
- package/libs/chunk-HRRHPLER.js.map +1 -0
- package/libs/chunk-IEB64SWY.js +8 -0
- package/libs/chunk-IEB64SWY.js.map +1 -0
- package/libs/{chunk-NGTJDDFO.js → chunk-IQ76HGVP.js} +2 -2
- package/libs/chunk-IRLFZ3OL.js +9 -0
- package/libs/chunk-IRLFZ3OL.js.map +1 -0
- package/libs/{chunk-JJ43O4Y5.js → chunk-KK47SYZI.js} +2 -2
- package/libs/chunk-O3JIHC5M.cjs +15 -0
- package/libs/chunk-O3JIHC5M.cjs.map +1 -0
- package/libs/chunk-O5XAJ7BY.cjs +18 -0
- package/libs/chunk-O5XAJ7BY.cjs.map +1 -0
- package/libs/chunk-OVWLQYMK.js +10 -0
- package/libs/chunk-OVWLQYMK.js.map +1 -0
- package/libs/chunk-PNWIRCG3.cjs +7 -0
- package/libs/chunk-PNWIRCG3.cjs.map +1 -0
- package/libs/{chunk-D4YLRWAO.cjs → chunk-QVW6W76L.cjs} +6 -6
- package/libs/chunk-T4T6GWYQ.cjs +17 -0
- package/libs/chunk-T4T6GWYQ.cjs.map +1 -0
- package/libs/chunk-TON2YGMD.cjs +9 -0
- package/libs/chunk-TON2YGMD.cjs.map +1 -0
- package/libs/chunk-UEPAWMDF.js +8 -0
- package/libs/chunk-UEPAWMDF.js.map +1 -0
- package/libs/{chunk-LT5KZ2QW.cjs → chunk-US2I5GI7.cjs} +3 -3
- package/libs/{chunk-B7F5FS6D.cjs → chunk-W2UIN7EV.cjs} +3 -3
- package/libs/{chunk-P2DC76ZZ.cjs → chunk-W5TKWBFC.cjs} +3 -3
- package/libs/chunk-WXBFBWYF.cjs +16 -0
- package/libs/chunk-WXBFBWYF.cjs.map +1 -0
- package/libs/{chunk-VUH3FXGJ.js → chunk-X3JCTEPD.js} +5 -5
- package/libs/chunk-X5LGFCWG.js +9 -0
- package/libs/chunk-X5LGFCWG.js.map +1 -0
- package/libs/{chunk-5M57K4SW.js → chunk-Y2PFDELK.js} +2 -2
- package/libs/{chunk-ETFLFC2S.js → chunk-ZFJ4U45S.js} +2 -2
- package/libs/{component-props-a8a2f97e.d.ts → component-props-67d978a2.d.ts} +4 -4
- package/libs/components/alert/alert.css +1 -1
- package/libs/components/alert/alert.css.map +1 -1
- package/libs/components/alert/alert.min.css +2 -2
- package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
- package/libs/components/breadcrumbs/breadcrumb.d.cts +11 -11
- package/libs/components/breadcrumbs/breadcrumb.d.ts +11 -11
- package/libs/components/breadcrumbs/breadcrumb.js +3 -3
- package/libs/components/button.cjs +6 -4
- package/libs/components/button.d.cts +97 -4
- package/libs/components/button.d.ts +97 -4
- package/libs/components/button.js +4 -2
- package/libs/components/card.cjs +7 -7
- package/libs/components/card.d.cts +14 -14
- package/libs/components/card.d.ts +14 -14
- package/libs/components/card.js +2 -2
- package/libs/components/dialog/dialog.cjs +9 -7
- package/libs/components/dialog/dialog.d.cts +3 -3
- package/libs/components/dialog/dialog.d.ts +3 -3
- package/libs/components/dialog/dialog.js +7 -5
- package/libs/components/form/fields.cjs +4 -4
- package/libs/components/form/fields.d.cts +16 -7
- package/libs/components/form/fields.d.ts +16 -7
- package/libs/components/form/fields.js +2 -2
- package/libs/components/form/inputs.cjs +6 -4
- package/libs/components/form/inputs.d.cts +50 -2
- package/libs/components/form/inputs.d.ts +50 -2
- package/libs/components/form/inputs.js +4 -2
- package/libs/components/form/textarea.cjs +5 -4
- package/libs/components/form/textarea.d.cts +32 -23
- package/libs/components/form/textarea.d.ts +32 -23
- package/libs/components/form/textarea.js +3 -2
- package/libs/components/heading/heading.cjs +3 -3
- package/libs/components/heading/heading.d.cts +2 -2
- package/libs/components/heading/heading.d.ts +2 -2
- package/libs/components/heading/heading.js +2 -2
- package/libs/components/icons/icon.cjs +4 -4
- package/libs/components/icons/icon.d.cts +38 -38
- package/libs/components/icons/icon.d.ts +38 -38
- package/libs/components/icons/icon.js +2 -2
- package/libs/components/link/link.cjs +4 -4
- package/libs/components/link/link.css +1 -1
- package/libs/components/link/link.css.map +1 -1
- package/libs/components/link/link.d.cts +3 -19
- package/libs/components/link/link.d.ts +3 -19
- package/libs/components/link/link.js +2 -2
- package/libs/components/link/link.min.css +2 -2
- package/libs/components/list/list.cjs +5 -5
- package/libs/components/list/list.css +1 -0
- package/libs/components/list/list.css.map +1 -0
- package/libs/components/list/list.d.cts +120 -33
- package/libs/components/list/list.d.ts +120 -33
- package/libs/components/list/list.js +2 -2
- package/libs/components/list/list.min.css +3 -0
- package/libs/components/modal.cjs +6 -4
- package/libs/components/modal.d.cts +8 -8
- package/libs/components/modal.d.ts +8 -8
- package/libs/components/modal.js +5 -3
- package/libs/components/nav/nav.cjs +7 -7
- package/libs/components/nav/nav.css +1 -1
- package/libs/components/nav/nav.css.map +1 -1
- package/libs/components/nav/nav.d.cts +550 -34
- package/libs/components/nav/nav.d.ts +550 -34
- package/libs/components/nav/nav.js +3 -3
- package/libs/components/nav/nav.min.css +2 -2
- package/libs/components/popover/popover.d.cts +5 -5
- package/libs/components/popover/popover.d.ts +5 -5
- package/libs/components/tables/table.cjs +5 -5
- package/libs/components/tables/table.d.cts +8 -8
- package/libs/components/tables/table.d.ts +8 -8
- package/libs/components/tables/table.js +2 -2
- package/libs/components/tag/tag.css +1 -1
- package/libs/components/tag/tag.css.map +1 -1
- package/libs/components/tag/tag.min.css +2 -2
- package/libs/components/text/text.cjs +5 -5
- package/libs/components/text/text.d.cts +5 -5
- package/libs/components/text/text.d.ts +5 -5
- package/libs/components/text/text.js +2 -2
- package/libs/form.types-d25ebfac.d.ts +233 -0
- package/libs/{heading-3648c538.d.ts → heading-7446cb46.d.ts} +8 -8
- package/libs/hooks.cjs +9 -4
- package/libs/hooks.d.cts +137 -3
- package/libs/hooks.d.ts +137 -3
- package/libs/hooks.js +4 -3
- package/libs/icons.cjs +3 -3
- package/libs/icons.d.cts +2 -2
- package/libs/icons.d.ts +2 -2
- package/libs/icons.js +2 -2
- package/libs/index.cjs +53 -51
- package/libs/index.cjs.map +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +338 -49
- package/libs/index.d.ts +338 -49
- package/libs/index.js +24 -22
- package/libs/index.js.map +1 -1
- package/libs/link-5192f411.d.ts +323 -0
- package/libs/list.types-d26de310.d.ts +245 -0
- package/libs/{ui-645f95b5.d.ts → ui-d01b50d4.d.ts} +16 -12
- package/package.json +4 -6
- package/src/components/alert/alert.scss +1 -4
- package/src/components/breadcrumbs/breadcrumb.tsx +4 -1
- package/src/components/buttons/README.mdx +102 -1
- package/src/components/buttons/button.stories.tsx +106 -0
- package/src/components/buttons/button.tsx +82 -52
- package/src/components/dialog/dialog-a11y-review.md +653 -0
- package/src/components/form/README.mdx +725 -43
- package/src/components/form/WCAG-REVIEW.md +654 -0
- package/src/components/form/fields.tsx +10 -1
- package/src/components/form/form.stories.tsx +604 -23
- package/src/components/form/form.tsx +204 -63
- package/src/components/form/form.types.ts +378 -0
- package/src/components/form/input.stories.tsx +71 -3
- package/src/components/form/inputs.tsx +159 -67
- package/src/components/form/select.tsx +122 -66
- package/src/components/form/textarea.tsx +120 -73
- package/src/components/fp.tsx +86 -11
- package/src/components/link/README.mdx +923 -0
- package/src/components/link/link.scss +79 -26
- package/src/components/link/link.stories.tsx +383 -30
- package/src/components/link/link.test.tsx +677 -0
- package/src/components/link/link.tsx +163 -57
- package/src/components/link/link.types.ts +261 -0
- package/src/components/list/README.mdx +764 -0
- package/src/components/list/list.scss +285 -0
- package/src/components/list/list.stories.tsx +514 -27
- package/src/components/list/list.test.tsx +554 -0
- package/src/components/list/list.tsx +153 -51
- package/src/components/list/list.types.ts +255 -0
- package/src/components/nav/ACCESSIBILITY.md +649 -0
- package/src/components/nav/README.mdx +782 -0
- package/src/components/nav/nav.scss +37 -4
- package/src/components/nav/nav.stories.tsx +44 -6
- package/src/components/nav/nav.tsx +302 -51
- package/src/components/nav/nav.types.ts +308 -0
- package/src/components/tag/README.mdx +426 -0
- package/src/components/tag/tag.scss +101 -27
- package/src/components/tag/tag.stories.tsx +384 -10
- package/src/components/tag/tag.test.tsx +210 -0
- package/src/components/tag/tag.tsx +106 -9
- package/src/components/tag/tag.types.ts +107 -0
- package/src/components/ui.tsx +8 -3
- package/src/hooks/use-disabled-state.test.tsx +536 -0
- package/src/hooks/use-disabled-state.ts +246 -0
- package/src/hooks/useDisabledState.md +393 -0
- package/src/hooks.ts +6 -0
- package/src/index.scss +2 -0
- package/src/index.ts +2 -1
- package/src/sass/_globals.scss +2 -7
- package/src/styles/alert/alert.css +1 -3
- package/src/styles/alert/alert.css.map +1 -1
- package/src/styles/index.css +461 -81
- package/src/styles/index.css.map +1 -1
- package/src/styles/link/link.css +45 -28
- package/src/styles/link/link.css.map +1 -1
- package/src/styles/list/list.css +214 -0
- package/src/styles/list/list.css.map +1 -0
- package/src/styles/nav/nav.css +32 -6
- package/src/styles/nav/nav.css.map +1 -1
- package/src/styles/tag/tag.css +113 -35
- package/src/styles/tag/tag.css.map +1 -1
- package/src/styles/utilities/_disabled.scss +58 -0
- package/src/types/shared.ts +43 -6
- package/src/utils/accessibility.ts +109 -0
- package/libs/chunk-2LTJ7HHX.cjs +0 -18
- package/libs/chunk-2LTJ7HHX.cjs.map +0 -1
- package/libs/chunk-2Y7W75TT.js +0 -9
- package/libs/chunk-2Y7W75TT.js.map +0 -1
- package/libs/chunk-5S4ORA4C.cjs +0 -15
- package/libs/chunk-5S4ORA4C.cjs.map +0 -1
- package/libs/chunk-AHDJGCG5.cjs +0 -15
- package/libs/chunk-AHDJGCG5.cjs.map +0 -1
- package/libs/chunk-BHRQBJRY.js +0 -8
- package/libs/chunk-BHRQBJRY.js.map +0 -1
- package/libs/chunk-GZ4QFPRY.js +0 -9
- package/libs/chunk-GZ4QFPRY.js.map +0 -1
- package/libs/chunk-IYUN2EW3.cjs +0 -15
- package/libs/chunk-IYUN2EW3.cjs.map +0 -1
- package/libs/chunk-J32EZPYD.cjs +0 -15
- package/libs/chunk-J32EZPYD.cjs.map +0 -1
- package/libs/chunk-KUKIVRC2.js +0 -7
- package/libs/chunk-KUKIVRC2.js.map +0 -1
- package/libs/chunk-L75OQKEI.cjs.map +0 -1
- package/libs/chunk-M5RRNTVX.cjs +0 -15
- package/libs/chunk-M5RRNTVX.cjs.map +0 -1
- package/libs/chunk-OK5QEIMD.cjs +0 -17
- package/libs/chunk-OK5QEIMD.cjs.map +0 -1
- package/libs/chunk-P7TTEYCD.js +0 -7
- package/libs/chunk-P7TTEYCD.js.map +0 -1
- package/libs/chunk-QLZWHAMK.js +0 -8
- package/libs/chunk-QLZWHAMK.js.map +0 -1
- package/libs/chunk-RIVUMPOG.js +0 -8
- package/libs/chunk-RIVUMPOG.js.map +0 -1
- package/libs/chunk-S7BABR7Z.cjs +0 -13
- package/libs/chunk-S7BABR7Z.cjs.map +0 -1
- package/libs/chunk-SMYRLO3E.js +0 -8
- package/libs/chunk-SMYRLO3E.js.map +0 -1
- package/libs/chunk-TYRCEX2L.js +0 -8
- package/libs/chunk-TYRCEX2L.js.map +0 -1
- package/libs/chunk-XBA562WW.js +0 -8
- package/libs/chunk-XBA562WW.js.map +0 -1
- package/libs/chunk-XTQKWY7W.cjs +0 -32
- package/libs/chunk-XTQKWY7W.cjs.map +0 -1
- package/libs/inputs-f3a216db.d.ts +0 -45
- /package/libs/{chunk-PQ2K3BM6.cjs.map → chunk-2NRIP6RB.cjs.map} +0 -0
- /package/libs/{chunk-772NRB75.js.map → chunk-5QD3DWFI.js.map} +0 -0
- /package/libs/{chunk-3MKLDCKQ.cjs.map → chunk-6WTC4JXH.cjs.map} +0 -0
- /package/libs/{chunk-ZANSFMTD.js.map → chunk-7XPFW7CB.js.map} +0 -0
- /package/libs/{chunk-ROZI23GS.cjs.map → chunk-DKTHCQ5P.cjs.map} +0 -0
- /package/libs/{chunk-NGTJDDFO.js.map → chunk-IQ76HGVP.js.map} +0 -0
- /package/libs/{chunk-JJ43O4Y5.js.map → chunk-KK47SYZI.js.map} +0 -0
- /package/libs/{chunk-D4YLRWAO.cjs.map → chunk-QVW6W76L.cjs.map} +0 -0
- /package/libs/{chunk-LT5KZ2QW.cjs.map → chunk-US2I5GI7.cjs.map} +0 -0
- /package/libs/{chunk-B7F5FS6D.cjs.map → chunk-W2UIN7EV.cjs.map} +0 -0
- /package/libs/{chunk-P2DC76ZZ.cjs.map → chunk-W5TKWBFC.cjs.map} +0 -0
- /package/libs/{chunk-VUH3FXGJ.js.map → chunk-X3JCTEPD.js.map} +0 -0
- /package/libs/{chunk-5M57K4SW.js.map → chunk-Y2PFDELK.js.map} +0 -0
- /package/libs/{chunk-ETFLFC2S.js.map → chunk-ZFJ4U45S.js.map} +0 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# Form Components - WCAG 2.1 AA Accessibility Review
|
|
2
|
+
|
|
3
|
+
**Review Date:** 2025-10-25
|
|
4
|
+
**Components Reviewed:** Form, Field, Input, Select, Textarea
|
|
5
|
+
**WCAG Version:** 2.1 Level AA
|
|
6
|
+
**Files Analyzed:**
|
|
7
|
+
- `form.tsx`
|
|
8
|
+
- `fields.tsx`
|
|
9
|
+
- `inputs.tsx`
|
|
10
|
+
- `select.tsx`
|
|
11
|
+
- `textarea.tsx`
|
|
12
|
+
- `form.types.ts`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Executive Summary
|
|
17
|
+
|
|
18
|
+
The Form component and its sub-components demonstrate strong accessibility foundations with proper semantic HTML, ARIA attributes, and keyboard support. However, there are several critical issues that need attention to achieve full WCAG 2.1 AA compliance.
|
|
19
|
+
|
|
20
|
+
**Issues Found:** 4 errors, 3 warnings, 2 recommendations
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Critical Issues (Errors)
|
|
25
|
+
|
|
26
|
+
### 1. Disabled State Implementation - WCAG 4.1.2 (Name, Role, Value)
|
|
27
|
+
|
|
28
|
+
**Severity:** Error
|
|
29
|
+
**Files Affected:** `inputs.tsx:142`, `select.tsx:139`, `textarea.tsx:121`
|
|
30
|
+
**WCAG Criteria:** 4.1.2 Name, Role, Value (Level A)
|
|
31
|
+
|
|
32
|
+
#### Problem
|
|
33
|
+
|
|
34
|
+
All three input components use `aria-disabled` instead of the native `disabled` attribute. This is a significant accessibility violation because `aria-disabled` alone doesn't prevent user interaction—keyboard users can still focus and type in these inputs.
|
|
35
|
+
|
|
36
|
+
**inputs.tsx (Line 142):**
|
|
37
|
+
```tsx
|
|
38
|
+
// ❌ Bad - aria-disabled doesn't prevent interaction
|
|
39
|
+
aria-disabled={isInputDisabled}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**select.tsx (Line 139):**
|
|
43
|
+
```tsx
|
|
44
|
+
// ❌ Bad
|
|
45
|
+
aria-disabled={disabled}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**textarea.tsx (Line 121):**
|
|
49
|
+
```tsx
|
|
50
|
+
// ❌ Bad
|
|
51
|
+
aria-disabled={disabled}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Fix
|
|
55
|
+
|
|
56
|
+
Use the native `disabled` attribute, which provides both functionality AND proper semantics:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// ✅ Good - Input component
|
|
60
|
+
<FP
|
|
61
|
+
as="input"
|
|
62
|
+
// ... other props
|
|
63
|
+
disabled={isInputDisabled} // Native disabled attribute
|
|
64
|
+
// Remove aria-disabled - it's redundant with native disabled
|
|
65
|
+
aria-readonly={readOnly}
|
|
66
|
+
aria-required={required}
|
|
67
|
+
aria-invalid={isInvalid}
|
|
68
|
+
aria-describedby={ariaDescribedBy}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// ✅ Good - Select component
|
|
75
|
+
<UI
|
|
76
|
+
as="select"
|
|
77
|
+
// ... other props
|
|
78
|
+
disabled={disabled} // Native disabled attribute
|
|
79
|
+
// Remove aria-disabled
|
|
80
|
+
required={required}
|
|
81
|
+
aria-required={required}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
// ✅ Good - Textarea component
|
|
88
|
+
<UI
|
|
89
|
+
as="textarea"
|
|
90
|
+
// ... other props
|
|
91
|
+
disabled={disabled} // Native disabled attribute
|
|
92
|
+
// Remove aria-disabled
|
|
93
|
+
aria-required={required}
|
|
94
|
+
readOnly={readOnly}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Why This Matters
|
|
100
|
+
|
|
101
|
+
Screen readers announce the disabled state from the native attribute, AND the browser prevents interaction. Using only aria-disabled creates a "fake" disabled state that doesn't actually work.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### 2. Missing Validation ARIA in Select and Textarea - WCAG 3.3.1, 4.1.2
|
|
106
|
+
|
|
107
|
+
**Severity:** Error
|
|
108
|
+
**Files Affected:** `select.tsx`, `textarea.tsx`
|
|
109
|
+
**WCAG Criteria:** 3.3.1 Error Identification (Level A), 4.1.2 Name, Role, Value (Level A)
|
|
110
|
+
|
|
111
|
+
#### Problem
|
|
112
|
+
|
|
113
|
+
The TypeScript interfaces define `validationState`, `errorMessage`, and `hintText` props, but the Select and Textarea components don't implement `aria-invalid` or `aria-describedby` to expose these to assistive technologies. The Input component implements this correctly, but Select and Textarea don't.
|
|
114
|
+
|
|
115
|
+
**Current Select implementation (incomplete):**
|
|
116
|
+
```tsx
|
|
117
|
+
// select.tsx - missing validation ARIA
|
|
118
|
+
export interface SelectProps {
|
|
119
|
+
validationState?: ValidationState // ✅ Defined in types
|
|
120
|
+
errorMessage?: string // ✅ Defined in types
|
|
121
|
+
hintText?: string // ✅ Defined in types
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ❌ But not implemented in component!
|
|
125
|
+
<UI
|
|
126
|
+
as="select"
|
|
127
|
+
// Missing: aria-invalid, aria-describedby
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Fix for Select
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
// select.tsx
|
|
136
|
+
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
137
|
+
(
|
|
138
|
+
{
|
|
139
|
+
id,
|
|
140
|
+
validationState = 'none',
|
|
141
|
+
errorMessage,
|
|
142
|
+
hintText,
|
|
143
|
+
// ... other props
|
|
144
|
+
},
|
|
145
|
+
ref
|
|
146
|
+
) => {
|
|
147
|
+
// Determine aria-invalid based on validation state
|
|
148
|
+
const isInvalid = validationState === 'invalid';
|
|
149
|
+
|
|
150
|
+
// Generate describedby IDs for error and hint text
|
|
151
|
+
const describedByIds: string[] = [];
|
|
152
|
+
if (errorMessage && id) {
|
|
153
|
+
describedByIds.push(`${id}-error`);
|
|
154
|
+
}
|
|
155
|
+
if (hintText && id) {
|
|
156
|
+
describedByIds.push(`${id}-hint`);
|
|
157
|
+
}
|
|
158
|
+
const ariaDescribedBy =
|
|
159
|
+
describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<UI
|
|
163
|
+
as="select"
|
|
164
|
+
id={id}
|
|
165
|
+
aria-invalid={isInvalid}
|
|
166
|
+
aria-describedby={ariaDescribedBy}
|
|
167
|
+
aria-required={required}
|
|
168
|
+
disabled={disabled}
|
|
169
|
+
// ... other props
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Fix for Textarea
|
|
177
|
+
|
|
178
|
+
Apply the same pattern as above to textarea.tsx.
|
|
179
|
+
|
|
180
|
+
#### Why This Matters
|
|
181
|
+
|
|
182
|
+
When validation errors occur, screen reader users need to be notified. Without `aria-invalid` and `aria-describedby`, they have no way to discover errors.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### 3. Redundant ARIA Role on Option - WCAG 4.1.2
|
|
187
|
+
|
|
188
|
+
**Severity:** Error
|
|
189
|
+
**File:** `select.tsx:27`
|
|
190
|
+
**WCAG Criteria:** 4.1.2 Name, Role, Value (Level A)
|
|
191
|
+
|
|
192
|
+
#### Problem
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// ❌ Bad - role="option" is redundant on native <option>
|
|
196
|
+
export const Option = ({ selectValue, selectLabel }: SelectOptionsProps) => {
|
|
197
|
+
return (
|
|
198
|
+
<option role="option" value={selectValue}>
|
|
199
|
+
{selectLabel || selectValue}
|
|
200
|
+
</option>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Fix
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
// ✅ Good - remove redundant role
|
|
209
|
+
export const Option = ({ selectValue, selectLabel }: SelectOptionsProps) => {
|
|
210
|
+
return (
|
|
211
|
+
<option value={selectValue}>
|
|
212
|
+
{selectLabel || selectValue}
|
|
213
|
+
</option>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Why This Matters
|
|
219
|
+
|
|
220
|
+
Native HTML elements have implicit ARIA roles. Adding explicit roles can confuse assistive technologies and validators. The first rule of ARIA is "don't use ARIA if native HTML works."
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 4. Form Missing Accessible Name - WCAG 2.4.6, 4.1.2
|
|
225
|
+
|
|
226
|
+
**Severity:** Error (when multiple forms on page)
|
|
227
|
+
**File:** `form.tsx:164`
|
|
228
|
+
**WCAG Criteria:** 2.4.6 Headings and Labels (Level AA), 4.1.2 Name, Role, Value (Level A)
|
|
229
|
+
|
|
230
|
+
#### Problem
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
// form.tsx (Line 164)
|
|
234
|
+
<UI
|
|
235
|
+
as="form"
|
|
236
|
+
role="form" // ⚠️ Also redundant - native form has implicit role
|
|
237
|
+
aria-busy={isBusy}
|
|
238
|
+
// ❌ Missing: aria-label or aria-labelledby
|
|
239
|
+
{...props}
|
|
240
|
+
>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The JSDoc examples show `aria-label` usage, but the component doesn't require or encourage providing an accessible name. When multiple forms exist on a page, screen reader users can't distinguish between them.
|
|
244
|
+
|
|
245
|
+
#### Fix Option 1: Encourage aria-label via TypeScript (recommended)
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
export interface FormProps extends Omit<React.ComponentProps<'form'>, 'className'> {
|
|
249
|
+
/**
|
|
250
|
+
* Accessible name for the form (required for distinguishing multiple forms)
|
|
251
|
+
* @example "Contact form", "Login form", "Search form"
|
|
252
|
+
*/
|
|
253
|
+
'aria-label'?: string
|
|
254
|
+
'aria-labelledby'?: string
|
|
255
|
+
// ... other props
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Fix Option 2: Add runtime warning
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
const Form = React.forwardRef<HTMLFormElement, FormProps>(
|
|
263
|
+
({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }, ref) => {
|
|
264
|
+
// Warn in development if no accessible name provided
|
|
265
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
266
|
+
if (!ariaLabel && !ariaLabelledBy) {
|
|
267
|
+
console.warn(
|
|
268
|
+
'Form component should have an accessible name via aria-label or aria-labelledby'
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<UI
|
|
275
|
+
as="form"
|
|
276
|
+
// Remove redundant role="form"
|
|
277
|
+
aria-busy={isBusy}
|
|
278
|
+
aria-label={ariaLabel}
|
|
279
|
+
aria-labelledby={ariaLabelledBy}
|
|
280
|
+
{...props}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### Why This Matters
|
|
288
|
+
|
|
289
|
+
Screen reader users who navigate by landmarks need to know which form is which. "Form" alone isn't descriptive enough.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Warnings (Should Fix)
|
|
294
|
+
|
|
295
|
+
### 1. Redundant role="form" - WCAG 4.1.2
|
|
296
|
+
|
|
297
|
+
**Severity:** Warning
|
|
298
|
+
**File:** `form.tsx:164`
|
|
299
|
+
**WCAG Criteria:** 4.1.2 Name, Role, Value (Level A)
|
|
300
|
+
|
|
301
|
+
#### Issue
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
// ⚠️ Redundant - native <form> already has role="form"
|
|
305
|
+
<UI
|
|
306
|
+
as="form"
|
|
307
|
+
role="form" // Remove this
|
|
308
|
+
aria-busy={isBusy}
|
|
309
|
+
{...props}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Fix
|
|
314
|
+
|
|
315
|
+
Remove `role="form"` - the native `<form>` element already has an implicit role of "form".
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### 2. autoFocus Support - WCAG 3.2.1 (On Focus)
|
|
320
|
+
|
|
321
|
+
**Severity:** Warning
|
|
322
|
+
**Files:** `inputs.tsx:78`, `inputs.tsx:134`
|
|
323
|
+
**WCAG Criteria:** 3.2.1 On Focus (Level A)
|
|
324
|
+
|
|
325
|
+
#### Issue
|
|
326
|
+
|
|
327
|
+
The Input component supports `autoFocus={true}`, which can cause unexpected context changes and disorient users, especially screen reader users.
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
// inputs.tsx
|
|
331
|
+
autoFocus={autoFocus} // ⚠️ Can violate WCAG 3.2.1
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Recommendation
|
|
335
|
+
|
|
336
|
+
Consider adding a JSDoc warning:
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
/**
|
|
340
|
+
* Auto-focus on mount
|
|
341
|
+
* ⚠️ WARNING: Use sparingly. Can violate WCAG 3.2.1 if it causes unexpected context changes.
|
|
342
|
+
* Only use when user clearly expects focus (e.g., search page, modal dialogs)
|
|
343
|
+
* @default false
|
|
344
|
+
* @see https://www.w3.org/WAI/WCAG21/Understanding/on-focus.html
|
|
345
|
+
*/
|
|
346
|
+
autoFocus?: boolean
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### 3. Field Component Doesn't Validate Label Association
|
|
352
|
+
|
|
353
|
+
**Severity:** Warning
|
|
354
|
+
**File:** `fields.tsx:38`
|
|
355
|
+
**WCAG Criteria:** 3.3.2 Labels or Instructions (Level A)
|
|
356
|
+
|
|
357
|
+
#### Issue
|
|
358
|
+
|
|
359
|
+
The Field component accepts a `labelFor` prop but doesn't validate that:
|
|
360
|
+
1. `labelFor` is provided
|
|
361
|
+
2. A matching child input exists
|
|
362
|
+
|
|
363
|
+
This can lead to unlabeled inputs if developers forget to connect them properly.
|
|
364
|
+
|
|
365
|
+
**Current implementation:**
|
|
366
|
+
```tsx
|
|
367
|
+
// fields.tsx
|
|
368
|
+
<label htmlFor={labelFor}>{label}</label> // labelFor might be undefined
|
|
369
|
+
{children}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### Recommendation
|
|
373
|
+
|
|
374
|
+
Add TypeScript validation or runtime warning:
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
export interface FieldProps {
|
|
378
|
+
/**
|
|
379
|
+
* ID of the associated form control (REQUIRED for accessibility)
|
|
380
|
+
* Must match the id of the child input/select/textarea
|
|
381
|
+
*/
|
|
382
|
+
labelFor: string // Make required, not optional
|
|
383
|
+
// ... other props
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Recommendations (Best Practices)
|
|
390
|
+
|
|
391
|
+
### 1. Add Form Error Summary Pattern
|
|
392
|
+
|
|
393
|
+
**WCAG Criteria:** 3.3.1 Error Identification (Level A)
|
|
394
|
+
|
|
395
|
+
#### Benefit
|
|
396
|
+
|
|
397
|
+
Helps users with screen readers discover all validation errors at once.
|
|
398
|
+
|
|
399
|
+
#### Example
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
// Recommended pattern for form-level error summary
|
|
403
|
+
{errors.length > 0 && (
|
|
404
|
+
<div role="alert" aria-live="assertive" id="form-errors">
|
|
405
|
+
<h2>Please correct the following errors:</h2>
|
|
406
|
+
<ul>
|
|
407
|
+
{errors.map((error, index) => (
|
|
408
|
+
<li key={index}>
|
|
409
|
+
<a href={`#${error.fieldId}`}>{error.message}</a>
|
|
410
|
+
</li>
|
|
411
|
+
))}
|
|
412
|
+
</ul>
|
|
413
|
+
</div>
|
|
414
|
+
)}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
### 2. Document Required Autocomplete Usage - WCAG 1.3.5
|
|
420
|
+
|
|
421
|
+
**WCAG Criteria:** 1.3.5 Identify Input Purpose (Level AA)
|
|
422
|
+
|
|
423
|
+
#### Issue
|
|
424
|
+
|
|
425
|
+
The Input component supports `autoComplete`, but WCAG 2.1 AA requires autocomplete attributes for personal data inputs.
|
|
426
|
+
|
|
427
|
+
#### Recommendation
|
|
428
|
+
|
|
429
|
+
Add JSDoc guidance:
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
/**
|
|
433
|
+
* Autocomplete attribute for browser autofill
|
|
434
|
+
* REQUIRED by WCAG 2.1 AA (1.3.5) for inputs collecting user information:
|
|
435
|
+
* - "email" for email addresses
|
|
436
|
+
* - "tel" for phone numbers
|
|
437
|
+
* - "given-name", "family-name" for names
|
|
438
|
+
* - "street-address", "address-level1", etc. for addresses
|
|
439
|
+
* @see https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html
|
|
440
|
+
*/
|
|
441
|
+
autoComplete?: string
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Accessibility Strengths
|
|
447
|
+
|
|
448
|
+
The form components demonstrate several excellent accessibility practices:
|
|
449
|
+
|
|
450
|
+
### ✅ Proper Semantic HTML
|
|
451
|
+
- Uses native `<form>`, `<input>`, `<select>`, `<textarea>`, and `<label>` elements
|
|
452
|
+
- Leverages built-in browser accessibility features
|
|
453
|
+
|
|
454
|
+
### ✅ Keyboard Support
|
|
455
|
+
- All components are keyboard accessible
|
|
456
|
+
- `onEnter` handlers enable keyboard-only form submission
|
|
457
|
+
- Textarea properly handles Shift+Enter for new lines
|
|
458
|
+
|
|
459
|
+
### ✅ Forward Refs
|
|
460
|
+
- All components properly forward refs using `React.forwardRef`
|
|
461
|
+
- Enables parent components to manage focus programmatically
|
|
462
|
+
|
|
463
|
+
### ✅ Comprehensive TypeScript Types
|
|
464
|
+
- Well-defined prop interfaces with JSDoc comments
|
|
465
|
+
- Extends native HTML element props for maximum flexibility
|
|
466
|
+
- Provides type safety for accessibility attributes
|
|
467
|
+
|
|
468
|
+
### ✅ Validation State Management (Input component)
|
|
469
|
+
- Input component correctly implements `aria-invalid`
|
|
470
|
+
- Properly associates error/hint text with `aria-describedby`
|
|
471
|
+
- Generates unique IDs for error messages
|
|
472
|
+
|
|
473
|
+
### ✅ Documentation
|
|
474
|
+
- Excellent JSDoc comments with examples
|
|
475
|
+
- References to WCAG success criteria in code comments
|
|
476
|
+
- Multiple usage examples demonstrating accessibility patterns
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Testing Recommendations
|
|
481
|
+
|
|
482
|
+
### Automated Testing
|
|
483
|
+
|
|
484
|
+
#### 1. Install and configure eslint-plugin-jsx-a11y
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
npm install --save-dev eslint-plugin-jsx-a11y
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Add to `.eslintrc`:
|
|
491
|
+
```json
|
|
492
|
+
{
|
|
493
|
+
"extends": ["plugin:jsx-a11y/recommended"],
|
|
494
|
+
"rules": {
|
|
495
|
+
"jsx-a11y/no-redundant-roles": "error",
|
|
496
|
+
"jsx-a11y/aria-props": "error",
|
|
497
|
+
"jsx-a11y/aria-proptypes": "error",
|
|
498
|
+
"jsx-a11y/label-has-associated-control": "error"
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
#### 2. Add jest-axe for component testing
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
npm install --save-dev jest-axe
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Example test:
|
|
510
|
+
```tsx
|
|
511
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
512
|
+
import { render } from '@testing-library/react';
|
|
513
|
+
import Form from './form';
|
|
514
|
+
|
|
515
|
+
expect.extend(toHaveNoViolations);
|
|
516
|
+
|
|
517
|
+
describe('Form Accessibility', () => {
|
|
518
|
+
it('should have no accessibility violations', async () => {
|
|
519
|
+
const { container } = render(
|
|
520
|
+
<Form aria-label="Contact form">
|
|
521
|
+
<Form.Field label="Email" labelFor="email">
|
|
522
|
+
<Form.Input id="email" type="email" autoComplete="email" />
|
|
523
|
+
</Form.Field>
|
|
524
|
+
<Form.Field label="Message" labelFor="message">
|
|
525
|
+
<Form.Textarea id="message" name="message" />
|
|
526
|
+
</Form.Field>
|
|
527
|
+
</Form>
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const results = await axe(container);
|
|
531
|
+
expect(results).toHaveNoViolations();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should announce validation errors', async () => {
|
|
535
|
+
const { container } = render(
|
|
536
|
+
<Form aria-label="Contact form">
|
|
537
|
+
<Form.Field label="Email" labelFor="email">
|
|
538
|
+
<Form.Input
|
|
539
|
+
id="email"
|
|
540
|
+
type="email"
|
|
541
|
+
validationState="invalid"
|
|
542
|
+
errorMessage="Please enter a valid email"
|
|
543
|
+
/>
|
|
544
|
+
<div id="email-error" role="alert">
|
|
545
|
+
Please enter a valid email
|
|
546
|
+
</div>
|
|
547
|
+
</Form.Field>
|
|
548
|
+
</Form>
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const results = await axe(container);
|
|
552
|
+
expect(results).toHaveNoViolations();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Manual Testing Checklist
|
|
558
|
+
|
|
559
|
+
#### Keyboard Navigation
|
|
560
|
+
- [ ] Tab through all form fields without mouse
|
|
561
|
+
- [ ] Verify tab order matches visual order
|
|
562
|
+
- [ ] Test Enter key submission
|
|
563
|
+
- [ ] Test Shift+Enter in textarea (creates new line)
|
|
564
|
+
- [ ] Verify disabled inputs cannot receive focus
|
|
565
|
+
- [ ] Check focus indicators are visible (3:1 contrast)
|
|
566
|
+
|
|
567
|
+
#### Screen Reader Testing
|
|
568
|
+
- [ ] Test with NVDA (Windows) or VoiceOver (Mac)
|
|
569
|
+
- [ ] Verify all labels are announced
|
|
570
|
+
- [ ] Check error messages are announced when inputs become invalid
|
|
571
|
+
- [ ] Verify disabled/required states are announced
|
|
572
|
+
- [ ] Test form landmark navigation
|
|
573
|
+
- [ ] Verify form has an accessible name
|
|
574
|
+
|
|
575
|
+
#### Validation & Error Handling
|
|
576
|
+
- [ ] Verify errors are announced immediately when they occur
|
|
577
|
+
- [ ] Check `aria-invalid` updates dynamically
|
|
578
|
+
- [ ] Test that error messages are associated with inputs
|
|
579
|
+
- [ ] Verify error color has sufficient contrast
|
|
580
|
+
- [ ] Check errors are indicated by more than color alone
|
|
581
|
+
|
|
582
|
+
#### Form Submission
|
|
583
|
+
- [ ] Test Enter key submission from text inputs
|
|
584
|
+
- [ ] Verify aria-busy announces during submission
|
|
585
|
+
- [ ] Test form submission with validation errors
|
|
586
|
+
- [ ] Check success/error messages are announced
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Quick Wins
|
|
591
|
+
|
|
592
|
+
These fixes provide the most significant accessibility improvements with minimal effort:
|
|
593
|
+
|
|
594
|
+
1. **Replace `aria-disabled` with native `disabled`** in Input, Select, and Textarea
|
|
595
|
+
- **Time:** 5 minutes
|
|
596
|
+
- **Impact:** Critical - fixes keyboard trap vulnerability
|
|
597
|
+
|
|
598
|
+
2. **Remove redundant `role="form"` and `role="option"`**
|
|
599
|
+
- **Time:** 2 minutes
|
|
600
|
+
- **Impact:** High - eliminates ARIA violations
|
|
601
|
+
|
|
602
|
+
3. **Add validation ARIA to Select and Textarea** (copy pattern from Input)
|
|
603
|
+
- **Time:** 15 minutes
|
|
604
|
+
- **Impact:** Critical - enables error announcement for screen readers
|
|
605
|
+
|
|
606
|
+
4. **Add `aria-label` to FormProps TypeScript interface**
|
|
607
|
+
- **Time:** 5 minutes
|
|
608
|
+
- **Impact:** Medium - improves form landmark navigation
|
|
609
|
+
|
|
610
|
+
**Total implementation time: ~30 minutes for major compliance improvements**
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Implementation Priority
|
|
615
|
+
|
|
616
|
+
### Priority 1 (Critical - Fix Immediately)
|
|
617
|
+
1. Replace `aria-disabled` with native `disabled` attribute
|
|
618
|
+
2. Add validation ARIA to Select and Textarea components
|
|
619
|
+
|
|
620
|
+
### Priority 2 (High - Fix Before Release)
|
|
621
|
+
3. Remove redundant ARIA roles
|
|
622
|
+
4. Add form accessible name requirement
|
|
623
|
+
|
|
624
|
+
### Priority 3 (Medium - Next Sprint)
|
|
625
|
+
5. Make `labelFor` required in Field component
|
|
626
|
+
6. Add autoFocus JSDoc warning
|
|
627
|
+
7. Add autocomplete JSDoc guidance
|
|
628
|
+
|
|
629
|
+
### Priority 4 (Low - Future Enhancement)
|
|
630
|
+
8. Add error summary pattern example to docs
|
|
631
|
+
9. Add runtime warnings for missing accessibility attributes
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Additional Resources
|
|
636
|
+
|
|
637
|
+
- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/?versions=2.1&levels=aa)
|
|
638
|
+
- [ARIA Authoring Practices Guide - Forms](https://www.w3.org/WAI/ARIA/apg/patterns/)
|
|
639
|
+
- [WebAIM: Creating Accessible Forms](https://webaim.org/techniques/forms/)
|
|
640
|
+
- [MDN: ARIA Form Role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/form_role)
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## Review Summary
|
|
645
|
+
|
|
646
|
+
**Overall Assessment:** The form components are well-architected with strong accessibility foundations. The critical issues identified are straightforward to fix and mostly involve replacing ARIA attributes with native HTML attributes. Once these issues are addressed, the components will be fully WCAG 2.1 AA compliant.
|
|
647
|
+
|
|
648
|
+
**Reviewer Confidence:** High - All issues have clear fixes with code examples provided.
|
|
649
|
+
|
|
650
|
+
**Next Steps:**
|
|
651
|
+
1. Address the 4 critical errors
|
|
652
|
+
2. Run automated tests with jest-axe
|
|
653
|
+
3. Perform manual keyboard and screen reader testing
|
|
654
|
+
4. Update Storybook examples to demonstrate accessible usage patterns
|
|
@@ -6,12 +6,21 @@ export type FieldProps = {
|
|
|
6
6
|
* The label content
|
|
7
7
|
*/
|
|
8
8
|
label: React.ReactNode
|
|
9
|
+
/**
|
|
10
|
+
* ID of the associated form control (REQUIRED for accessibility)
|
|
11
|
+
* Must match the id of the child input/select/textarea element.
|
|
12
|
+
* This ensures proper label-to-input association for screen readers.
|
|
13
|
+
* @example "email-input"
|
|
14
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html|WCAG 3.3.2 Labels or Instructions}
|
|
15
|
+
*/
|
|
16
|
+
labelFor: string
|
|
9
17
|
children: React.ReactNode
|
|
10
18
|
} & React.ComponentProps<'label'> &
|
|
11
19
|
Partial<React.ComponentProps<typeof UI>>
|
|
12
20
|
/**
|
|
13
21
|
* Field component that renders a label and children wrapped in a div element.
|
|
14
|
-
*
|
|
22
|
+
* Ensures proper accessibility by requiring the labelFor prop to associate labels with form controls.
|
|
23
|
+
* @param labelFor Defines the for attribute of the label element (REQUIRED)
|
|
15
24
|
* @param styles Custom styles to be applied to the component
|
|
16
25
|
* @param label The label content
|
|
17
26
|
* @param children The children to be rendered inside the component
|