@fpkit/acss 0.5.12 → 0.5.13

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 (264) hide show
  1. package/README.md +89 -0
  2. package/libs/{chunk-DV56L5YX.cjs → chunk-2LTJ7HHX.cjs} +4 -4
  3. package/libs/{chunk-EQ67LF46.js → chunk-2Y7W75TT.js} +3 -3
  4. package/libs/{chunk-KKLTUJFB.cjs → chunk-3MKLDCKQ.cjs} +5 -5
  5. package/libs/chunk-3MKLDCKQ.cjs.map +1 -0
  6. package/libs/{chunk-X3EVB7VS.cjs → chunk-5S4ORA4C.cjs} +3 -3
  7. package/libs/{chunk-O6QZBB6G.js → chunk-772NRB75.js} +5 -5
  8. package/libs/chunk-772NRB75.js.map +1 -0
  9. package/libs/{chunk-6BVXFW7U.cjs → chunk-AHDJGCG5.cjs} +3 -3
  10. package/libs/{chunk-E3XP6BEX.cjs → chunk-B7F5FS6D.cjs} +3 -3
  11. package/libs/chunk-D4YLRWAO.cjs +18 -0
  12. package/libs/chunk-D4YLRWAO.cjs.map +1 -0
  13. package/libs/chunk-ETFLFC2S.js +10 -0
  14. package/libs/chunk-ETFLFC2S.js.map +1 -0
  15. package/libs/chunk-GZ4QFPRY.js +9 -0
  16. package/libs/chunk-GZ4QFPRY.js.map +1 -0
  17. package/libs/{chunk-LHVJKDMA.cjs → chunk-J32EZPYD.cjs} +3 -3
  18. package/libs/chunk-JJ43O4Y5.js +8 -0
  19. package/libs/chunk-JJ43O4Y5.js.map +1 -0
  20. package/libs/chunk-KUKIVRC2.js +7 -0
  21. package/libs/chunk-KUKIVRC2.js.map +1 -0
  22. package/libs/chunk-L75OQKEI.cjs +13 -0
  23. package/libs/chunk-L75OQKEI.cjs.map +1 -0
  24. package/libs/{chunk-LL7HTLMS.cjs → chunk-M5RRNTVX.cjs} +3 -3
  25. package/libs/{chunk-LIQJ7ZZR.js → chunk-NGTJDDFO.js} +2 -2
  26. package/libs/chunk-OK5QEIMD.cjs +17 -0
  27. package/libs/chunk-OK5QEIMD.cjs.map +1 -0
  28. package/libs/chunk-P2DC76ZZ.cjs +18 -0
  29. package/libs/chunk-P2DC76ZZ.cjs.map +1 -0
  30. package/libs/chunk-PQ2K3BM6.cjs +17 -0
  31. package/libs/chunk-PQ2K3BM6.cjs.map +1 -0
  32. package/libs/{chunk-QCMV4VQZ.js → chunk-QLZWHAMK.js} +2 -2
  33. package/libs/{chunk-BIP2NY53.js → chunk-RIVUMPOG.js} +2 -2
  34. package/libs/{chunk-ICCKQ2GC.cjs → chunk-ROZI23GS.cjs} +4 -4
  35. package/libs/{chunk-NHYXGV3L.js → chunk-SMYRLO3E.js} +2 -2
  36. package/libs/{chunk-5ZM4XL44.js → chunk-TYRCEX2L.js} +2 -2
  37. package/libs/chunk-VUH3FXGJ.js +11 -0
  38. package/libs/chunk-VUH3FXGJ.js.map +1 -0
  39. package/libs/{chunk-PPOOBUOS.js → chunk-XBA562WW.js} +2 -2
  40. package/libs/{chunk-QVV34QEH.cjs → chunk-XTQKWY7W.cjs} +3 -3
  41. package/libs/{chunk-YWOYVRFT.js → chunk-ZANSFMTD.js} +3 -3
  42. package/libs/components/alert/alert.css +1 -1
  43. package/libs/components/alert/alert.css.map +1 -1
  44. package/libs/components/alert/alert.min.css +2 -2
  45. package/libs/components/badge/badge.css +1 -1
  46. package/libs/components/badge/badge.css.map +1 -1
  47. package/libs/components/badge/badge.min.css +2 -2
  48. package/libs/components/breadcrumbs/breadcrumb.cjs +9 -5
  49. package/libs/components/breadcrumbs/breadcrumb.d.cts +271 -32
  50. package/libs/components/breadcrumbs/breadcrumb.d.ts +271 -32
  51. package/libs/components/breadcrumbs/breadcrumb.js +3 -3
  52. package/libs/components/button.cjs +4 -4
  53. package/libs/components/button.d.cts +2 -2
  54. package/libs/components/button.d.ts +2 -2
  55. package/libs/components/button.js +2 -2
  56. package/libs/components/buttons/button.css +1 -1
  57. package/libs/components/buttons/button.css.map +1 -1
  58. package/libs/components/buttons/button.min.css +2 -2
  59. package/libs/components/card.cjs +7 -7
  60. package/libs/components/card.d.cts +277 -33
  61. package/libs/components/card.d.ts +277 -33
  62. package/libs/components/card.js +2 -2
  63. package/libs/components/cards/card.css +1 -1
  64. package/libs/components/cards/card.css.map +1 -1
  65. package/libs/components/cards/card.min.css +2 -2
  66. package/libs/components/details/details.css +1 -1
  67. package/libs/components/details/details.css.map +1 -1
  68. package/libs/components/details/details.min.css +2 -2
  69. package/libs/components/dialog/dialog.cjs +7 -7
  70. package/libs/components/dialog/dialog.css +1 -1
  71. package/libs/components/dialog/dialog.css.map +1 -1
  72. package/libs/components/dialog/dialog.d.cts +88 -34
  73. package/libs/components/dialog/dialog.d.ts +88 -34
  74. package/libs/components/dialog/dialog.js +5 -5
  75. package/libs/components/dialog/dialog.min.css +2 -2
  76. package/libs/components/form/fields.cjs +4 -4
  77. package/libs/components/form/fields.d.cts +2 -2
  78. package/libs/components/form/fields.d.ts +2 -2
  79. package/libs/components/form/fields.js +2 -2
  80. package/libs/components/form/textarea.cjs +4 -4
  81. package/libs/components/form/textarea.d.cts +2 -2
  82. package/libs/components/form/textarea.d.ts +2 -2
  83. package/libs/components/form/textarea.js +2 -2
  84. package/libs/components/heading/heading.cjs +3 -3
  85. package/libs/components/heading/heading.d.cts +3 -14
  86. package/libs/components/heading/heading.d.ts +3 -14
  87. package/libs/components/heading/heading.js +2 -2
  88. package/libs/components/icons/icon.cjs +4 -4
  89. package/libs/components/icons/icon.d.cts +148 -4
  90. package/libs/components/icons/icon.d.ts +148 -4
  91. package/libs/components/icons/icon.js +2 -2
  92. package/libs/components/images/img.css +1 -1
  93. package/libs/components/images/img.css.map +1 -1
  94. package/libs/components/images/img.min.css +2 -2
  95. package/libs/components/link/link.cjs +4 -4
  96. package/libs/components/link/link.d.cts +2 -2
  97. package/libs/components/link/link.d.ts +2 -2
  98. package/libs/components/link/link.js +2 -2
  99. package/libs/components/list/list.cjs +5 -5
  100. package/libs/components/list/list.d.cts +3 -3
  101. package/libs/components/list/list.d.ts +3 -3
  102. package/libs/components/list/list.js +2 -2
  103. package/libs/components/modal.cjs +4 -4
  104. package/libs/components/modal.js +3 -3
  105. package/libs/components/nav/nav.cjs +7 -7
  106. package/libs/components/nav/nav.d.cts +2 -2
  107. package/libs/components/nav/nav.d.ts +2 -2
  108. package/libs/components/nav/nav.js +3 -3
  109. package/libs/components/text/text.cjs +5 -5
  110. package/libs/components/text/text.d.cts +2 -2
  111. package/libs/components/text/text.d.ts +2 -2
  112. package/libs/components/text/text.js +2 -2
  113. package/libs/heading-3648c538.d.ts +250 -0
  114. package/libs/hooks.cjs +7 -0
  115. package/libs/hooks.d.cts +5 -0
  116. package/libs/hooks.d.ts +5 -0
  117. package/libs/hooks.js +3 -0
  118. package/libs/icons.cjs +3 -3
  119. package/libs/icons.d.cts +1 -1
  120. package/libs/icons.d.ts +1 -1
  121. package/libs/icons.js +2 -2
  122. package/libs/index.cjs +112 -91
  123. package/libs/index.cjs.map +1 -1
  124. package/libs/index.css +1 -1
  125. package/libs/index.css.map +1 -1
  126. package/libs/index.d.cts +515 -31
  127. package/libs/index.d.ts +515 -31
  128. package/libs/index.js +31 -19
  129. package/libs/index.js.map +1 -1
  130. package/libs/ui-645f95b5.d.ts +285 -0
  131. package/package.json +2 -83
  132. package/src/components/README-UI.mdx +416 -0
  133. package/src/components/alert/ACCESSIBILITY.md +319 -0
  134. package/src/components/alert/README.mdx +475 -19
  135. package/src/components/alert/alert.scss +113 -6
  136. package/src/components/alert/alert.stories.tsx +372 -0
  137. package/src/components/alert/alert.test.tsx +762 -0
  138. package/src/components/alert/alert.tsx +331 -66
  139. package/src/components/alert/views/alert-actions.tsx +13 -0
  140. package/src/components/alert/views/alert-content.tsx +17 -0
  141. package/src/components/alert/views/alert-icon.tsx +53 -0
  142. package/src/components/alert/views/alert-screen-reader-text.tsx +30 -0
  143. package/src/components/alert/views/alert-title.tsx +23 -0
  144. package/src/components/alert/views/alert-view.tsx +158 -0
  145. package/src/components/alert/views/index.ts +12 -0
  146. package/src/components/badge/badge.mdx +186 -49
  147. package/src/components/badge/badge.scss +20 -2
  148. package/src/components/badge/badge.stories.tsx +160 -14
  149. package/src/components/badge/badge.test.tsx +179 -0
  150. package/src/components/badge/badge.tsx +97 -4
  151. package/src/components/breadcrumbs/README.mdx +364 -45
  152. package/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +152 -0
  153. package/src/components/breadcrumbs/breadcrumb.stories.tsx +7 -3
  154. package/src/components/breadcrumbs/breadcrumb.test.tsx +490 -0
  155. package/src/components/breadcrumbs/breadcrumb.tsx +427 -170
  156. package/src/components/buttons/button.scss +34 -31
  157. package/src/components/buttons/button.stories.tsx +35 -0
  158. package/src/components/cards/README.mdx +657 -0
  159. package/src/components/cards/card.scss +22 -0
  160. package/src/components/cards/card.stories.tsx +167 -5
  161. package/src/components/cards/card.test.tsx +360 -20
  162. package/src/components/cards/card.tsx +200 -79
  163. package/src/components/cards/card.types.ts +135 -0
  164. package/src/components/cards/card.utils.ts +79 -0
  165. package/src/components/details/ACCESSIBILITY-REVIEW-LIVE.md +1050 -0
  166. package/src/components/details/ACCESSIBILITY-REVIEW.md +502 -0
  167. package/src/components/details/README.mdx +437 -69
  168. package/src/components/details/details.scss +16 -7
  169. package/src/components/details/details.test.tsx +385 -0
  170. package/src/components/details/details.tsx +101 -69
  171. package/src/components/details/details.types.ts +76 -0
  172. package/src/components/dialog/README.mdx +513 -110
  173. package/src/components/dialog/dialog-modal.tsx +79 -56
  174. package/src/components/dialog/dialog.scss +53 -3
  175. package/src/components/dialog/dialog.stories.tsx +10 -7
  176. package/src/components/dialog/dialog.test.tsx +450 -0
  177. package/src/components/dialog/dialog.tsx +69 -59
  178. package/src/components/dialog/dialog.types.ts +133 -0
  179. package/src/components/dialog/views/dialog-footer.tsx +54 -11
  180. package/src/components/dialog/views/dialog-header.tsx +20 -15
  181. package/src/components/heading/heading.stories.tsx +44 -4
  182. package/src/components/heading/heading.tsx +89 -23
  183. package/src/components/icons/README.mdx +332 -0
  184. package/src/components/icons/icon.stories.tsx +74 -1
  185. package/src/components/icons/icon.tsx +89 -1
  186. package/src/components/icons/types.ts +47 -0
  187. package/src/components/images/README.mdx +340 -24
  188. package/src/components/images/img.scss +19 -3
  189. package/src/components/images/img.stories.tsx +424 -15
  190. package/src/components/images/img.test.tsx +354 -25
  191. package/src/components/images/img.tsx +186 -63
  192. package/src/components/images/img.types.ts +211 -0
  193. package/src/components/title/MIGRATION.md +199 -0
  194. package/src/components/title/README.md +326 -0
  195. package/src/components/title/README.mdx +452 -0
  196. package/src/components/title/title.stories.tsx +393 -0
  197. package/src/components/title/title.test.tsx +251 -0
  198. package/src/components/title/title.tsx +219 -0
  199. package/src/components/ui.stories.tsx +894 -0
  200. package/src/components/ui.test.tsx +559 -0
  201. package/src/components/ui.tsx +266 -15
  202. package/src/components/word-count/README.md +240 -0
  203. package/src/hooks.ts +1 -0
  204. package/src/index.ts +10 -2
  205. package/src/sass/_properties.scss +1 -0
  206. package/src/styles/alert/alert.css +94 -4
  207. package/src/styles/alert/alert.css.map +1 -1
  208. package/src/styles/badge/badge.css +20 -2
  209. package/src/styles/badge/badge.css.map +1 -1
  210. package/src/styles/buttons/button.css +31 -31
  211. package/src/styles/buttons/button.css.map +1 -1
  212. package/src/styles/cards/card.css +16 -0
  213. package/src/styles/cards/card.css.map +1 -1
  214. package/src/styles/details/details.css +19 -8
  215. package/src/styles/details/details.css.map +1 -1
  216. package/src/styles/dialog/dialog.css +43 -2
  217. package/src/styles/dialog/dialog.css.map +1 -1
  218. package/src/styles/images/img.css +15 -3
  219. package/src/styles/images/img.css.map +1 -1
  220. package/src/styles/index.css +240 -51
  221. package/src/styles/index.css.map +1 -1
  222. package/src/test/setup.d.ts +9 -0
  223. package/src/test/setup.ts +53 -1
  224. package/libs/chunk-6TE5QEVE.cjs +0 -13
  225. package/libs/chunk-6TE5QEVE.cjs.map +0 -1
  226. package/libs/chunk-7K76RW2A.cjs +0 -18
  227. package/libs/chunk-7K76RW2A.cjs.map +0 -1
  228. package/libs/chunk-BSPKFLO4.js +0 -8
  229. package/libs/chunk-BSPKFLO4.js.map +0 -1
  230. package/libs/chunk-BV5CLH44.cjs +0 -18
  231. package/libs/chunk-BV5CLH44.cjs.map +0 -1
  232. package/libs/chunk-DKGJHKGW.js +0 -9
  233. package/libs/chunk-DKGJHKGW.js.map +0 -1
  234. package/libs/chunk-ECLD37WN.cjs +0 -16
  235. package/libs/chunk-ECLD37WN.cjs.map +0 -1
  236. package/libs/chunk-HYBZBN4G.js +0 -8
  237. package/libs/chunk-HYBZBN4G.js.map +0 -1
  238. package/libs/chunk-KKLTUJFB.cjs.map +0 -1
  239. package/libs/chunk-M5QL5TAE.cjs +0 -14
  240. package/libs/chunk-M5QL5TAE.cjs.map +0 -1
  241. package/libs/chunk-NE6YXTMC.js +0 -7
  242. package/libs/chunk-NE6YXTMC.js.map +0 -1
  243. package/libs/chunk-O6QZBB6G.js.map +0 -1
  244. package/libs/chunk-SXVZSWX6.js +0 -11
  245. package/libs/chunk-SXVZSWX6.js.map +0 -1
  246. package/libs/ui-9a6f9f8d.d.ts +0 -24
  247. package/src/components/cards/README.md +0 -80
  248. package/src/components/dialog/hooks/useClickOutside.ts +0 -33
  249. /package/libs/{chunk-DV56L5YX.cjs.map → chunk-2LTJ7HHX.cjs.map} +0 -0
  250. /package/libs/{chunk-EQ67LF46.js.map → chunk-2Y7W75TT.js.map} +0 -0
  251. /package/libs/{chunk-X3EVB7VS.cjs.map → chunk-5S4ORA4C.cjs.map} +0 -0
  252. /package/libs/{chunk-6BVXFW7U.cjs.map → chunk-AHDJGCG5.cjs.map} +0 -0
  253. /package/libs/{chunk-E3XP6BEX.cjs.map → chunk-B7F5FS6D.cjs.map} +0 -0
  254. /package/libs/{chunk-LHVJKDMA.cjs.map → chunk-J32EZPYD.cjs.map} +0 -0
  255. /package/libs/{chunk-LL7HTLMS.cjs.map → chunk-M5RRNTVX.cjs.map} +0 -0
  256. /package/libs/{chunk-LIQJ7ZZR.js.map → chunk-NGTJDDFO.js.map} +0 -0
  257. /package/libs/{chunk-QCMV4VQZ.js.map → chunk-QLZWHAMK.js.map} +0 -0
  258. /package/libs/{chunk-BIP2NY53.js.map → chunk-RIVUMPOG.js.map} +0 -0
  259. /package/libs/{chunk-ICCKQ2GC.cjs.map → chunk-ROZI23GS.cjs.map} +0 -0
  260. /package/libs/{chunk-NHYXGV3L.js.map → chunk-SMYRLO3E.js.map} +0 -0
  261. /package/libs/{chunk-5ZM4XL44.js.map → chunk-TYRCEX2L.js.map} +0 -0
  262. /package/libs/{chunk-PPOOBUOS.js.map → chunk-XBA562WW.js.map} +0 -0
  263. /package/libs/{chunk-QVV34QEH.cjs.map → chunk-XTQKWY7W.cjs.map} +0 -0
  264. /package/libs/{chunk-YWOYVRFT.js.map → chunk-ZANSFMTD.js.map} +0 -0
@@ -0,0 +1,502 @@
1
+ # 🔍 Accessibility Review: Details Component
2
+
3
+ **Review Date:** 2025-10-20
4
+ **WCAG Version:** 2.1 Level AA
5
+ **Reviewer:** Claude Code (WCAG Compliance Reviewer)
6
+
7
+ ---
8
+
9
+ ## Executive Summary
10
+
11
+ **Overall Rating: ✅ Excellent** - The Details component demonstrates strong accessibility practices by leveraging native HTML `<details>` and `<summary>` elements, which provide built-in keyboard support and ARIA semantics.
12
+
13
+ **Issues Found:** 0 errors, 2 warnings, 3 recommendations
14
+
15
+ ---
16
+
17
+ ## ✅ What's Working Well
18
+
19
+ ### 1. **Semantic HTML Foundation (WCAG 4.1.2, 1.3.1)**
20
+ The component uses native `<details>` and `<summary>` elements, which provide:
21
+ - Built-in `aria-expanded` state management by the browser
22
+ - Native keyboard support (Space, Enter)
23
+ - Automatic screen reader announcements as "disclosure" or "expandable" widget
24
+ - Proper role semantics without additional ARIA
25
+
26
+ ### 2. **Keyboard Accessibility (WCAG 2.1.1)**
27
+ ✅ Native `<details>` element handles all keyboard interactions
28
+ ✅ No custom JavaScript required for basic keyboard functionality
29
+ ✅ No keyboard traps
30
+
31
+ ### 3. **Focus Indicators (WCAG 2.4.7)**
32
+ From `details.scss:77-81`:
33
+ ```scss
34
+ &:focus-within {
35
+ outline: none;
36
+ border-bottom: solid 2px currentColor;
37
+ background-color: whitesmoke;
38
+ }
39
+ ```
40
+ ✅ Custom focus indicator with visible border and background change
41
+ ✅ Uses `currentColor` which adapts to text color
42
+
43
+ ### 4. **Component Forwarding (React Best Practice)**
44
+ From `details.tsx:54`:
45
+ ```tsx
46
+ export const Details = React.forwardRef<HTMLDetailsElement, DetailsProps>(
47
+ ```
48
+ ✅ Properly forwards refs to enable programmatic focus management
49
+
50
+ ### 5. **Accordion Behavior (WCAG 4.1.2)**
51
+ The component supports the native `name` attribute for accordion groups where only one details can be open at a time - this is a modern HTML feature with excellent accessibility.
52
+
53
+ ---
54
+
55
+ ## ⚠️ Warnings (Should Fix)
56
+
57
+ ### Warning 1: Focus Indicator Contrast Not Guaranteed
58
+
59
+ **Location:** `details.scss:77-81`
60
+
61
+ **Issue (WCAG 2.4.7 - Level AA):**
62
+ ```scss
63
+ &:focus-within {
64
+ outline: none; // ← Removes native outline
65
+ border-bottom: solid 2px currentColor; // ← Uses currentColor
66
+ background-color: whitesmoke;
67
+ }
68
+ ```
69
+
70
+ **Problem:**
71
+ - The custom focus indicator uses `currentColor`, which means the border color depends on the text color
72
+ - If text color has low contrast with the background, the focus indicator might fall below the required 3:1 contrast ratio
73
+ - `whitesmoke` (#f5f5f5) background might not provide sufficient contrast in all color schemes
74
+
75
+ **Recommended Fix:**
76
+ ```scss
77
+ &:focus-within {
78
+ outline: 2px solid #0066CC; /* Specific high-contrast color */
79
+ outline-offset: 2px;
80
+ background-color: #e8f4f8; /* Light blue tint */
81
+ }
82
+
83
+ /* Or use focus-visible for better UX */
84
+ &:focus-visible {
85
+ outline: 2px solid #0066CC;
86
+ outline-offset: 2px;
87
+ background-color: #e8f4f8;
88
+ }
89
+
90
+ &:focus:not(:focus-visible) {
91
+ outline: none;
92
+ }
93
+ ```
94
+
95
+ **Why:** WCAG 2.4.7 requires focus indicators to have at least 3:1 contrast ratio against adjacent colors. Using a specific color ensures this requirement is consistently met.
96
+
97
+ ---
98
+
99
+ ### Warning 2: Optional `aria-label` May Cause Confusion
100
+
101
+ **Location:** `details.tsx:94`
102
+
103
+ **Issue (WCAG 4.1.2 - Best Practice):**
104
+ ```tsx
105
+ <UI
106
+ as="details"
107
+ aria-label={ariaLabel} // ← Applied to <details> element
108
+ ...>
109
+ ```
110
+
111
+ **Problem:**
112
+ - The native `<details>` element already has proper semantics
113
+ - Adding `aria-label` to the `<details>` element might override the automatic screen reader announcement
114
+ - The label should typically be on the `<summary>` element, not the `<details>` wrapper
115
+ - Most screen readers announce: "Disclosure: [summary content], collapsed/expanded"
116
+
117
+ **Current Type Definition:** `details.types.ts:49-61`
118
+ ```tsx
119
+ /**
120
+ * Accessible label for screen readers.
121
+ * If not provided, the native `<details>` semantic will be used.
122
+ *
123
+ * Note: Native `<details>` elements are already semantic and announced properly
124
+ * by screen readers. Only provide this if you need to override the default behavior.
125
+ */
126
+ ariaLabel?: string;
127
+ ```
128
+
129
+ **Recommended Fix:**
130
+
131
+ Option A - Remove `aria-label` from details (preferred):
132
+ ```tsx
133
+ <UI
134
+ as="details"
135
+ // Remove aria-label from details element
136
+ ...>
137
+ <UI
138
+ as="summary"
139
+ onPointerDown={handlePointerDown}
140
+ aria-label={ariaLabel} // Move here if needed
141
+ >
142
+ ```
143
+
144
+ Option B - Keep current implementation with better documentation:
145
+ The documentation is actually quite good! It warns users this should rarely be used. Consider moving the `ariaLabel` to apply to the summary element instead for better screen reader support.
146
+
147
+ **Why:** Adding `aria-label` to `<details>` can interfere with the native semantic announcement. If additional labeling is needed, it should be on the `<summary>` element.
148
+
149
+ ---
150
+
151
+ ## 💡 Recommendations (Best Practices)
152
+
153
+ ### Recommendation 1: Add `aria-controls` Relationship
154
+
155
+ **Enhancement:** Strengthen the relationship between summary and content for screen readers.
156
+
157
+ **Suggested Implementation:**
158
+ ```tsx
159
+ export const Details = React.forwardRef<HTMLDetailsElement, DetailsProps>(
160
+ ({ summary, icon, styles, classes, ariaLabel, name, open, onPointerDown, onToggle, children, ...props }, ref) => {
161
+ const contentId = React.useId(); // Generate unique ID
162
+
163
+ return (
164
+ <UI as="details" ...>
165
+ <UI
166
+ as="summary"
167
+ onPointerDown={handlePointerDown}
168
+ aria-controls={contentId} // ← Links to content
169
+ >
170
+ {icon}
171
+ {summary}
172
+ </UI>
173
+ <UI as="section" id={contentId}> {/* ← Receives ID */}
174
+ {children}
175
+ </UI>
176
+ </UI>
177
+ );
178
+ }
179
+ );
180
+ ```
181
+
182
+ **Benefit:** Explicitly declares the relationship between trigger and content for assistive technologies.
183
+
184
+ ---
185
+
186
+ ### Recommendation 2: Consider `aria-hidden` for Decorative Icons
187
+
188
+ **Enhancement:** If the `icon` prop is purely decorative (like a chevron), it should be hidden from screen readers.
189
+
190
+ **Current Usage Example:**
191
+ ```tsx
192
+ <Details
193
+ summary="Shipping Information"
194
+ icon={<ChevronDownIcon />} // ← Should this be announced?
195
+ >
196
+ ```
197
+
198
+ **Recommendation:**
199
+ Add guidance in documentation or automatically apply `aria-hidden`:
200
+
201
+ ```tsx
202
+ // Option 1: Document in examples
203
+ <Details
204
+ summary="Shipping Information"
205
+ icon={<ChevronDownIcon aria-hidden="true" />}
206
+ >
207
+
208
+ // Option 2: Wrap icon automatically (recommended)
209
+ <UI as="summary" onPointerDown={handlePointerDown}>
210
+ {icon && <span aria-hidden="true">{icon}</span>}
211
+ {summary}
212
+ </UI>
213
+ ```
214
+
215
+ **Why:** Decorative icons add visual context but should not be announced by screen readers, as the summary text already conveys the meaning (WCAG 1.1.1).
216
+
217
+ ---
218
+
219
+ ### Recommendation 3: Add Example with ARIA Live Region for Dynamic Content
220
+
221
+ **Enhancement:** Document how to handle dynamically loaded content within details.
222
+
223
+ **Add to JSDoc documentation:**
224
+ ```tsx
225
+ /**
226
+ * @example
227
+ * ```tsx
228
+ * // Details with async content loading
229
+ * <Details summary="Load More Data" onToggle={handleLoadData}>
230
+ * {isLoading ? (
231
+ * <div role="status" aria-live="polite">Loading...</div>
232
+ * ) : (
233
+ * <div>{data}</div>
234
+ * )}
235
+ * </Details>
236
+ * ```
237
+ */
238
+ ```
239
+
240
+ **Why:** Users may load content dynamically when details open. Screen readers need to be informed of loading states (WCAG 4.1.3).
241
+
242
+ ---
243
+
244
+ ## 🎨 CSS Accessibility Review
245
+
246
+ ### ✅ Good Practices Found:
247
+
248
+ 1. **rem Units Throughout** - Text and spacing use relative units for zoom compatibility (WCAG 1.4.4)
249
+ 2. **CSS Custom Properties** - Themeable design supports user customization (WCAG 1.4.12)
250
+ 3. **Smooth Transitions** - Visual feedback for state changes aids understanding
251
+ 4. **Marker Removal** - Custom styling without breaking semantics
252
+ 5. **Responsive Design** - Proper border and radius handling for stacked details
253
+
254
+ ### ⚠️ CSS Concern: Text Spacing Support (WCAG 1.4.12)
255
+
256
+ The component should be tested with increased text spacing requirements:
257
+ ```css
258
+ /* Test with these overrides */
259
+ * {
260
+ line-height: 1.5 !important;
261
+ letter-spacing: 0.12em !important;
262
+ word-spacing: 0.16em !important;
263
+ margin-bottom: 2em !important; /* Paragraph spacing */
264
+ }
265
+ ```
266
+
267
+ **Recommendation:** Add to testing documentation to ensure content doesn't overflow or become clipped.
268
+
269
+ ---
270
+
271
+ ## 🧪 Testing Recommendations
272
+
273
+ ### Automated Testing
274
+
275
+ **Install Dependencies:**
276
+ ```bash
277
+ npm install --save-dev jest-axe @axe-core/react @testing-library/user-event
278
+ ```
279
+
280
+ **Component Test Example:**
281
+ ```tsx
282
+ import { render } from '@testing-library/react';
283
+ import userEvent from '@testing-library/user-event';
284
+ import { axe, toHaveNoViolations } from 'jest-axe';
285
+ import { Details } from './details';
286
+
287
+ expect.extend(toHaveNoViolations);
288
+
289
+ describe('Details - Accessibility', () => {
290
+ test('has no accessibility violations', async () => {
291
+ const { container } = render(
292
+ <Details summary="Test Summary">
293
+ <p>Test content</p>
294
+ </Details>
295
+ );
296
+ const results = await axe(container);
297
+ expect(results).toHaveNoViolations();
298
+ });
299
+
300
+ test('is keyboard accessible', async () => {
301
+ const user = userEvent.setup();
302
+ const { getByText } = render(
303
+ <Details summary="Click to expand">
304
+ <p>Hidden content</p>
305
+ </Details>
306
+ );
307
+
308
+ const summary = getByText('Click to expand');
309
+
310
+ // Tab to summary
311
+ await user.tab();
312
+ expect(summary).toHaveFocus();
313
+
314
+ // Press Enter to open
315
+ await user.keyboard('{Enter}');
316
+ expect(getByText('Hidden content')).toBeVisible();
317
+ });
318
+
319
+ test('accordion mode works correctly', async () => {
320
+ const { container } = render(
321
+ <>
322
+ <Details name="accordion" summary="Item 1">Content 1</Details>
323
+ <Details name="accordion" summary="Item 2" open>Content 2</Details>
324
+ <Details name="accordion" summary="Item 3">Content 3</Details>
325
+ </>
326
+ );
327
+
328
+ const allDetails = container.querySelectorAll('details');
329
+ const openDetails = container.querySelectorAll('details[open]');
330
+
331
+ expect(allDetails).toHaveLength(3);
332
+ expect(openDetails).toHaveLength(1);
333
+ });
334
+ });
335
+ ```
336
+
337
+ ### Manual Testing Checklist
338
+
339
+ #### Keyboard Navigation
340
+ - [ ] Tab to summary - should receive focus with visible indicator
341
+ - [ ] Press Space - should toggle open/closed
342
+ - [ ] Press Enter - should toggle open/closed
343
+ - [ ] Tab when open - should move to content inside
344
+ - [ ] Shift+Tab - should move focus backwards correctly
345
+ - [ ] No keyboard traps when navigating through content
346
+
347
+ #### Screen Reader Testing (NVDA/VoiceOver/JAWS)
348
+ - [ ] Announces as "disclosure", "expandable", or similar widget type
349
+ - [ ] Announces current state (expanded/collapsed)
350
+ - [ ] Summary text is read correctly
351
+ - [ ] Content is accessible and announced when expanded
352
+ - [ ] Icon (if present) is not announced if decorative
353
+ - [ ] State changes are announced when toggling
354
+
355
+ #### Visual Testing
356
+ - [ ] Focus indicator visible with sufficient contrast (minimum 3:1)
357
+ - [ ] Focus indicator works in dark mode (if applicable)
358
+ - [ ] Works at 200% browser zoom without horizontal scrolling
359
+ - [ ] Content reflows properly at 320px viewport width
360
+ - [ ] Works with increased text spacing (1.5 line-height, 0.12em letter-spacing)
361
+ - [ ] Custom CSS properties can be overridden for theming
362
+
363
+ #### Accordion Mode Testing
364
+ - [ ] Only one details open at a time when using `name` attribute
365
+ - [ ] Opening one closes the others in the same group
366
+ - [ ] State changes announced to screen readers
367
+ - [ ] Keyboard navigation works between accordion items
368
+
369
+ #### Browser Testing
370
+ - [ ] Chrome/Edge (latest)
371
+ - [ ] Firefox (latest)
372
+ - [ ] Safari (latest)
373
+ - [ ] Mobile Safari (iOS)
374
+ - [ ] Chrome Mobile (Android)
375
+
376
+ ---
377
+
378
+ ## 📊 WCAG 2.1 AA Compliance Summary
379
+
380
+ | Criterion | Level | Status | Notes |
381
+ |-----------|-------|--------|-------|
382
+ | **1.1.1 Non-text Content** | A | ✅ Pass | No images in component core; icon handling documented |
383
+ | **1.3.1 Info & Relationships** | A | ✅ Pass | Semantic `<details>` and `<summary>` elements |
384
+ | **1.3.2 Meaningful Sequence** | A | ✅ Pass | Logical DOM order maintained |
385
+ | **1.4.3 Contrast (Minimum)** | AA | ⚠️ Warning | Focus indicator contrast not guaranteed with `currentColor` |
386
+ | **1.4.10 Reflow** | AA | ✅ Pass | Uses flexible layout; recommend testing at 320px |
387
+ | **1.4.11 Non-text Contrast** | AA | ⚠️ Warning | Focus indicator needs contrast verification |
388
+ | **1.4.12 Text Spacing** | AA | ✅ Pass | Uses rem units; recommend manual testing |
389
+ | **1.4.13 Content on Hover/Focus** | AA | N/A | No hover/focus tooltips |
390
+ | **2.1.1 Keyboard** | A | ✅ Pass | Native keyboard support via `<details>` |
391
+ | **2.1.2 No Keyboard Trap** | A | ✅ Pass | No traps detected |
392
+ | **2.4.3 Focus Order** | A | ✅ Pass | Logical DOM and focus order |
393
+ | **2.4.7 Focus Visible** | AA | ⚠️ Warning | Custom focus indicator needs guaranteed contrast |
394
+ | **3.2.1 On Focus** | A | ✅ Pass | No unexpected context changes |
395
+ | **3.2.2 On Input** | A | ✅ Pass | Toggle requires explicit activation |
396
+ | **4.1.2 Name, Role, Value** | A | ✅ Pass | Native semantics provide all required properties |
397
+ | **4.1.3 Status Messages** | AA | 💡 Recommend | Document pattern for dynamic content loading |
398
+
399
+ **Legend:**
400
+ - ✅ Pass - Meets requirement
401
+ - ⚠️ Warning - Needs attention to ensure compliance
402
+ - 💡 Recommend - Optional enhancement
403
+ - N/A - Not applicable to this component
404
+
405
+ ---
406
+
407
+ ## 🎯 Quick Wins (Priority Fixes)
408
+
409
+ ### High Priority (5-15 minutes total)
410
+
411
+ 1. **Update focus indicator for guaranteed contrast** (5 min)
412
+ ```scss
413
+ summary {
414
+ &:focus-visible {
415
+ outline: 2px solid #0066CC;
416
+ outline-offset: 2px;
417
+ background-color: #e8f4f8;
418
+ }
419
+
420
+ &:focus:not(:focus-visible) {
421
+ outline: none;
422
+ }
423
+ }
424
+ ```
425
+
426
+ 2. **Consider moving `ariaLabel` to summary or removing** (10 min)
427
+ - Review if `aria-label` on `<details>` is truly needed
428
+ - If needed, apply to `<summary>` instead
429
+ - Update TypeScript types accordingly
430
+
431
+ ### Medium Priority (15-30 minutes)
432
+
433
+ 3. **Add `aria-controls` relationship** (10 min)
434
+ - Use `React.useId()` to generate unique IDs
435
+ - Link summary to content section
436
+
437
+ 4. **Auto-hide decorative icons from screen readers** (10 min)
438
+ ```tsx
439
+ {icon && <span aria-hidden="true">{icon}</span>}
440
+ ```
441
+
442
+ 5. **Add automated accessibility tests** (10 min)
443
+ - Install jest-axe
444
+ - Add basic axe tests to existing test file
445
+
446
+ ### Low Priority (Optional Enhancements)
447
+
448
+ 6. **Document dynamic content patterns** (5 min)
449
+ - Add JSDoc examples for loading states
450
+ - Show ARIA live region usage
451
+
452
+ 7. **Create comprehensive manual test checklist** (Already done in this document!)
453
+
454
+ ---
455
+
456
+ ## 📚 Additional Resources
457
+
458
+ ### WCAG Guidelines
459
+ - [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/?versions=2.1&levels=aa)
460
+ - [Understanding WCAG 2.1](https://www.w3.org/WAI/WCAG21/Understanding/)
461
+ - [ARIA Authoring Practices Guide - Disclosure Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/)
462
+
463
+ ### HTML Details Element
464
+ - [MDN: The Details Disclosure Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
465
+ - [HTML Spec: The details element](https://html.spec.whatwg.org/multipage/interactive-elements.html#the-details-element)
466
+
467
+ ### Testing Tools
468
+ - [axe DevTools Browser Extension](https://www.deque.com/axe/devtools/)
469
+ - [WAVE Browser Extension](https://wave.webaim.org/extension/)
470
+ - [NVDA Screen Reader](https://www.nvaccess.org/) (Windows, Free)
471
+ - [VoiceOver Screen Reader](https://www.apple.com/accessibility/voiceover/) (macOS/iOS, Built-in)
472
+
473
+ ---
474
+
475
+ ## 💡 Key Insights
476
+
477
+ ### 1. Native HTML Wins
478
+ This component brilliantly leverages `<details>` and `<summary>`, which are among the most accessible HTML5 elements. The browser handles `aria-expanded`, keyboard events, and screen reader semantics automatically - no custom JavaScript needed! This is a perfect example of "use platform features first."
479
+
480
+ ### 2. Focus-visible Pattern
481
+ Modern CSS supports `:focus-visible` which shows focus indicators only for keyboard users (not mouse clicks), providing better UX without compromising accessibility. This is preferable to the older `:focus-within` approach used currently.
482
+
483
+ ### 3. Progressive Enhancement
484
+ The `name` attribute for accordion behavior is a modern HTML feature with excellent accessibility built-in. It's a perfect example of progressive enhancement where newer browsers get richer functionality while older browsers still work perfectly with independent details elements.
485
+
486
+ ---
487
+
488
+ ## Summary
489
+
490
+ The Details component is **already highly accessible** thanks to its use of semantic HTML. The warnings are minor and relate to ensuring consistent contrast in all theming scenarios. The recommendations are optional enhancements that would make an already excellent component even better.
491
+
492
+ **Estimated time to address all warnings:** 15-20 minutes
493
+ **Current accessibility grade:** A- (Excellent, with minor improvements possible)
494
+
495
+ ---
496
+
497
+ **Next Steps:**
498
+ 1. Review and address the two warnings (focus indicator contrast)
499
+ 2. Consider implementing the recommended enhancements
500
+ 3. Add automated accessibility tests to CI/CD pipeline
501
+ 4. Conduct manual screen reader testing with NVDA and VoiceOver
502
+ 5. Test with users who rely on assistive technologies (if possible)