@fpkit/acss 0.5.11 → 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 (312) hide show
  1. package/README.md +514 -18
  2. package/libs/chunk-23ANBDCR.js +8 -0
  3. package/libs/chunk-23ANBDCR.js.map +1 -0
  4. package/libs/chunk-2LTJ7HHX.cjs +18 -0
  5. package/libs/chunk-2LTJ7HHX.cjs.map +1 -0
  6. package/libs/chunk-2Y7W75TT.js +9 -0
  7. package/libs/chunk-2Y7W75TT.js.map +1 -0
  8. package/libs/chunk-3MKLDCKQ.cjs +31 -0
  9. package/libs/chunk-3MKLDCKQ.cjs.map +1 -0
  10. package/libs/chunk-5M57K4SW.js +8 -0
  11. package/libs/chunk-5M57K4SW.js.map +1 -0
  12. package/libs/chunk-5S4ORA4C.cjs +15 -0
  13. package/libs/chunk-5S4ORA4C.cjs.map +1 -0
  14. package/libs/chunk-772NRB75.js +9 -0
  15. package/libs/chunk-772NRB75.js.map +1 -0
  16. package/libs/chunk-AHDJGCG5.cjs +15 -0
  17. package/libs/chunk-AHDJGCG5.cjs.map +1 -0
  18. package/libs/chunk-B7F5FS6D.cjs +16 -0
  19. package/libs/chunk-B7F5FS6D.cjs.map +1 -0
  20. package/libs/chunk-BHRQBJRY.js +8 -0
  21. package/libs/chunk-BHRQBJRY.js.map +1 -0
  22. package/libs/chunk-D4YLRWAO.cjs +18 -0
  23. package/libs/chunk-D4YLRWAO.cjs.map +1 -0
  24. package/libs/chunk-ETFLFC2S.js +10 -0
  25. package/libs/chunk-ETFLFC2S.js.map +1 -0
  26. package/libs/chunk-G55UJ53G.cjs +16 -0
  27. package/libs/chunk-G55UJ53G.cjs.map +1 -0
  28. package/libs/chunk-GZ4QFPRY.js +9 -0
  29. package/libs/chunk-GZ4QFPRY.js.map +1 -0
  30. package/libs/chunk-IYUN2EW3.cjs +15 -0
  31. package/libs/chunk-IYUN2EW3.cjs.map +1 -0
  32. package/libs/chunk-J32EZPYD.cjs +15 -0
  33. package/libs/chunk-J32EZPYD.cjs.map +1 -0
  34. package/libs/chunk-JJ43O4Y5.js +8 -0
  35. package/libs/chunk-JJ43O4Y5.js.map +1 -0
  36. package/libs/chunk-KUKIVRC2.js +7 -0
  37. package/libs/chunk-KUKIVRC2.js.map +1 -0
  38. package/libs/chunk-L75OQKEI.cjs +13 -0
  39. package/libs/chunk-L75OQKEI.cjs.map +1 -0
  40. package/libs/chunk-LT5KZ2QW.cjs +22 -0
  41. package/libs/chunk-LT5KZ2QW.cjs.map +1 -0
  42. package/libs/chunk-M5RRNTVX.cjs +15 -0
  43. package/libs/chunk-M5RRNTVX.cjs.map +1 -0
  44. package/libs/chunk-NGTJDDFO.js +8 -0
  45. package/libs/chunk-NGTJDDFO.js.map +1 -0
  46. package/libs/chunk-OK5QEIMD.cjs +17 -0
  47. package/libs/chunk-OK5QEIMD.cjs.map +1 -0
  48. package/libs/chunk-P2DC76ZZ.cjs +18 -0
  49. package/libs/chunk-P2DC76ZZ.cjs.map +1 -0
  50. package/libs/chunk-P7TTEYCD.js +7 -0
  51. package/libs/chunk-P7TTEYCD.js.map +1 -0
  52. package/libs/chunk-PQ2K3BM6.cjs +17 -0
  53. package/libs/chunk-PQ2K3BM6.cjs.map +1 -0
  54. package/libs/chunk-QLZWHAMK.js +8 -0
  55. package/libs/chunk-QLZWHAMK.js.map +1 -0
  56. package/libs/chunk-RIVUMPOG.js +8 -0
  57. package/libs/chunk-RIVUMPOG.js.map +1 -0
  58. package/libs/chunk-ROZI23GS.cjs +15 -0
  59. package/libs/chunk-ROZI23GS.cjs.map +1 -0
  60. package/libs/chunk-S7BABR7Z.cjs +13 -0
  61. package/libs/chunk-S7BABR7Z.cjs.map +1 -0
  62. package/libs/chunk-SMYRLO3E.js +8 -0
  63. package/libs/chunk-SMYRLO3E.js.map +1 -0
  64. package/libs/chunk-TYRCEX2L.js +8 -0
  65. package/libs/chunk-TYRCEX2L.js.map +1 -0
  66. package/libs/chunk-VUH3FXGJ.js +11 -0
  67. package/libs/chunk-VUH3FXGJ.js.map +1 -0
  68. package/libs/chunk-XBA562WW.js +8 -0
  69. package/libs/chunk-XBA562WW.js.map +1 -0
  70. package/libs/chunk-XTQKWY7W.cjs +32 -0
  71. package/libs/chunk-XTQKWY7W.cjs.map +1 -0
  72. package/libs/chunk-ZANSFMTD.js +9 -0
  73. package/libs/chunk-ZANSFMTD.js.map +1 -0
  74. package/libs/component-props-a8a2f97e.d.ts +38 -0
  75. package/libs/components/alert/alert.css +1 -1
  76. package/libs/components/alert/alert.css.map +1 -1
  77. package/libs/components/alert/alert.min.css +2 -2
  78. package/libs/components/badge/badge.css +1 -1
  79. package/libs/components/badge/badge.css.map +1 -1
  80. package/libs/components/badge/badge.min.css +2 -2
  81. package/libs/components/breadcrumbs/breadcrumb.cjs +24 -0
  82. package/libs/components/breadcrumbs/breadcrumb.cjs.map +1 -0
  83. package/libs/components/breadcrumbs/breadcrumb.d.cts +290 -0
  84. package/libs/components/breadcrumbs/breadcrumb.d.ts +290 -0
  85. package/libs/components/breadcrumbs/breadcrumb.js +5 -0
  86. package/libs/components/breadcrumbs/breadcrumb.js.map +1 -0
  87. package/libs/components/button.cjs +19 -0
  88. package/libs/components/button.cjs.map +1 -0
  89. package/libs/components/button.d.cts +16 -0
  90. package/libs/components/button.d.ts +16 -0
  91. package/libs/components/button.js +4 -0
  92. package/libs/components/button.js.map +1 -0
  93. package/libs/components/buttons/button.css +1 -1
  94. package/libs/components/buttons/button.css.map +1 -1
  95. package/libs/components/buttons/button.min.css +2 -2
  96. package/libs/components/card.cjs +31 -0
  97. package/libs/components/card.cjs.map +1 -0
  98. package/libs/components/card.d.cts +302 -0
  99. package/libs/components/card.d.ts +302 -0
  100. package/libs/components/card.js +4 -0
  101. package/libs/components/card.js.map +1 -0
  102. package/libs/components/cards/card.css +1 -1
  103. package/libs/components/cards/card.css.map +1 -1
  104. package/libs/components/cards/card.min.css +2 -2
  105. package/libs/components/details/details.css +1 -1
  106. package/libs/components/details/details.css.map +1 -1
  107. package/libs/components/details/details.min.css +2 -2
  108. package/libs/components/dialog/dialog.cjs +22 -0
  109. package/libs/components/dialog/dialog.cjs.map +1 -0
  110. package/libs/components/dialog/dialog.css +1 -1
  111. package/libs/components/dialog/dialog.css.map +1 -1
  112. package/libs/components/dialog/dialog.d.cts +105 -0
  113. package/libs/components/dialog/dialog.d.ts +105 -0
  114. package/libs/components/dialog/dialog.js +7 -0
  115. package/libs/components/dialog/dialog.js.map +1 -0
  116. package/libs/components/dialog/dialog.min.css +2 -2
  117. package/libs/components/form/fields.cjs +19 -0
  118. package/libs/components/form/fields.cjs.map +1 -0
  119. package/libs/components/form/fields.d.cts +24 -0
  120. package/libs/components/form/fields.d.ts +24 -0
  121. package/libs/components/form/fields.js +4 -0
  122. package/libs/components/form/fields.js.map +1 -0
  123. package/libs/components/form/inputs.cjs +19 -0
  124. package/libs/components/form/inputs.cjs.map +1 -0
  125. package/libs/components/form/inputs.d.cts +2 -0
  126. package/libs/components/form/inputs.d.ts +2 -0
  127. package/libs/components/form/inputs.js +4 -0
  128. package/libs/components/form/inputs.js.map +1 -0
  129. package/libs/components/form/textarea.cjs +19 -0
  130. package/libs/components/form/textarea.cjs.map +1 -0
  131. package/libs/components/form/textarea.d.cts +29 -0
  132. package/libs/components/form/textarea.d.ts +29 -0
  133. package/libs/components/form/textarea.js +4 -0
  134. package/libs/components/form/textarea.js.map +1 -0
  135. package/libs/components/heading/heading.cjs +10 -0
  136. package/libs/components/heading/heading.cjs.map +1 -0
  137. package/libs/components/heading/heading.d.cts +3 -0
  138. package/libs/components/heading/heading.d.ts +3 -0
  139. package/libs/components/heading/heading.js +4 -0
  140. package/libs/components/heading/heading.js.map +1 -0
  141. package/libs/components/icons/icon.cjs +19 -0
  142. package/libs/components/icons/icon.cjs.map +1 -0
  143. package/libs/{icons-31ace3de.d.ts → components/icons/icon.d.cts} +151 -61
  144. package/libs/components/icons/icon.d.ts +445 -0
  145. package/libs/components/icons/icon.js +4 -0
  146. package/libs/components/icons/icon.js.map +1 -0
  147. package/libs/components/images/img.css +1 -1
  148. package/libs/components/images/img.css.map +1 -1
  149. package/libs/components/images/img.min.css +2 -2
  150. package/libs/components/link/link.cjs +19 -0
  151. package/libs/components/link/link.cjs.map +1 -0
  152. package/libs/components/link/link.d.cts +19 -0
  153. package/libs/components/link/link.d.ts +19 -0
  154. package/libs/components/link/link.js +4 -0
  155. package/libs/components/link/link.js.map +1 -0
  156. package/libs/components/list/list.cjs +23 -0
  157. package/libs/components/list/list.cjs.map +1 -0
  158. package/libs/components/list/list.d.cts +39 -0
  159. package/libs/components/list/list.d.ts +39 -0
  160. package/libs/components/list/list.js +4 -0
  161. package/libs/components/list/list.js.map +1 -0
  162. package/libs/components/modal.cjs +14 -0
  163. package/libs/components/modal.cjs.map +1 -0
  164. package/libs/components/modal.d.cts +35 -0
  165. package/libs/components/modal.d.ts +35 -0
  166. package/libs/components/modal.js +5 -0
  167. package/libs/components/modal.js.map +1 -0
  168. package/libs/components/nav/nav.cjs +28 -0
  169. package/libs/components/nav/nav.cjs.map +1 -0
  170. package/libs/components/nav/nav.d.cts +44 -0
  171. package/libs/components/nav/nav.d.ts +44 -0
  172. package/libs/components/nav/nav.js +5 -0
  173. package/libs/components/nav/nav.js.map +1 -0
  174. package/libs/components/popover/popover.cjs +23 -0
  175. package/libs/components/popover/popover.cjs.map +1 -0
  176. package/libs/components/popover/popover.d.cts +40 -0
  177. package/libs/components/popover/popover.d.ts +40 -0
  178. package/libs/components/popover/popover.js +4 -0
  179. package/libs/components/popover/popover.js.map +1 -0
  180. package/libs/components/tables/table.cjs +21 -0
  181. package/libs/components/tables/table.cjs.map +1 -0
  182. package/libs/components/tables/table.d.cts +36 -0
  183. package/libs/components/tables/table.d.ts +36 -0
  184. package/libs/components/tables/table.js +4 -0
  185. package/libs/components/tables/table.js.map +1 -0
  186. package/libs/components/text/text.cjs +23 -0
  187. package/libs/components/text/text.cjs.map +1 -0
  188. package/libs/components/text/text.d.cts +30 -0
  189. package/libs/components/text/text.d.ts +30 -0
  190. package/libs/components/text/text.js +4 -0
  191. package/libs/components/text/text.js.map +1 -0
  192. package/libs/heading-3648c538.d.ts +250 -0
  193. package/libs/hooks.cjs +7 -0
  194. package/libs/hooks.d.cts +5 -0
  195. package/libs/hooks.d.ts +5 -0
  196. package/libs/hooks.js +3 -0
  197. package/libs/icons.cjs +3 -2
  198. package/libs/icons.d.cts +3 -1
  199. package/libs/icons.d.ts +3 -1
  200. package/libs/icons.js +2 -1
  201. package/libs/index.cjs +174 -62
  202. package/libs/index.cjs.map +1 -1
  203. package/libs/index.css +1 -1
  204. package/libs/index.css.map +1 -1
  205. package/libs/index.d.cts +529 -446
  206. package/libs/index.d.ts +529 -446
  207. package/libs/index.js +36 -7
  208. package/libs/index.js.map +1 -1
  209. package/libs/inputs-f3a216db.d.ts +45 -0
  210. package/libs/ui-645f95b5.d.ts +285 -0
  211. package/package.json +2 -2
  212. package/src/components/README-UI.mdx +416 -0
  213. package/src/components/alert/ACCESSIBILITY.md +319 -0
  214. package/src/components/alert/README.mdx +475 -19
  215. package/src/components/alert/alert.scss +113 -6
  216. package/src/components/alert/alert.stories.tsx +372 -0
  217. package/src/components/alert/alert.test.tsx +762 -0
  218. package/src/components/alert/alert.tsx +331 -66
  219. package/src/components/alert/views/alert-actions.tsx +13 -0
  220. package/src/components/alert/views/alert-content.tsx +17 -0
  221. package/src/components/alert/views/alert-icon.tsx +53 -0
  222. package/src/components/alert/views/alert-screen-reader-text.tsx +30 -0
  223. package/src/components/alert/views/alert-title.tsx +23 -0
  224. package/src/components/alert/views/alert-view.tsx +158 -0
  225. package/src/components/alert/views/index.ts +12 -0
  226. package/src/components/badge/badge.mdx +186 -49
  227. package/src/components/badge/badge.scss +20 -2
  228. package/src/components/badge/badge.stories.tsx +160 -14
  229. package/src/components/badge/badge.test.tsx +179 -0
  230. package/src/components/badge/badge.tsx +97 -4
  231. package/src/components/breadcrumbs/README.mdx +364 -45
  232. package/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +152 -0
  233. package/src/components/breadcrumbs/breadcrumb.stories.tsx +7 -3
  234. package/src/components/breadcrumbs/breadcrumb.test.tsx +490 -0
  235. package/src/components/breadcrumbs/breadcrumb.tsx +427 -170
  236. package/src/components/button.ts +2 -0
  237. package/src/components/buttons/button.scss +34 -31
  238. package/src/components/buttons/button.stories.tsx +35 -0
  239. package/src/components/card.ts +2 -0
  240. package/src/components/cards/README.mdx +657 -0
  241. package/src/components/cards/card.scss +22 -0
  242. package/src/components/cards/card.stories.tsx +167 -5
  243. package/src/components/cards/card.test.tsx +360 -20
  244. package/src/components/cards/card.tsx +200 -79
  245. package/src/components/cards/card.types.ts +135 -0
  246. package/src/components/cards/card.utils.ts +79 -0
  247. package/src/components/details/ACCESSIBILITY-REVIEW-LIVE.md +1050 -0
  248. package/src/components/details/ACCESSIBILITY-REVIEW.md +502 -0
  249. package/src/components/details/README.mdx +437 -69
  250. package/src/components/details/details.scss +16 -0
  251. package/src/components/details/details.test.tsx +385 -0
  252. package/src/components/details/details.tsx +101 -69
  253. package/src/components/details/details.types.ts +76 -0
  254. package/src/components/dialog/README.mdx +513 -110
  255. package/src/components/dialog/dialog-modal.tsx +79 -56
  256. package/src/components/dialog/dialog.scss +53 -3
  257. package/src/components/dialog/dialog.stories.tsx +10 -7
  258. package/src/components/dialog/dialog.test.tsx +450 -0
  259. package/src/components/dialog/dialog.tsx +69 -59
  260. package/src/components/dialog/dialog.types.ts +133 -0
  261. package/src/components/dialog/views/dialog-footer.tsx +54 -11
  262. package/src/components/dialog/views/dialog-header.tsx +20 -15
  263. package/src/components/heading/heading.stories.tsx +44 -4
  264. package/src/components/heading/heading.tsx +89 -23
  265. package/src/components/icons/README.mdx +332 -0
  266. package/src/components/icons/icon.stories.tsx +74 -1
  267. package/src/components/icons/icon.tsx +89 -1
  268. package/src/components/icons/types.ts +47 -0
  269. package/src/components/images/README.mdx +340 -24
  270. package/src/components/images/img.scss +19 -3
  271. package/src/components/images/img.stories.tsx +424 -15
  272. package/src/components/images/img.test.tsx +354 -25
  273. package/src/components/images/img.tsx +186 -63
  274. package/src/components/images/img.types.ts +211 -0
  275. package/src/components/modal.ts +1 -0
  276. package/src/components/title/MIGRATION.md +199 -0
  277. package/src/components/title/README.md +326 -0
  278. package/src/components/title/README.mdx +452 -0
  279. package/src/components/title/title.stories.tsx +393 -0
  280. package/src/components/title/title.test.tsx +251 -0
  281. package/src/components/title/title.tsx +219 -0
  282. package/src/components/ui.stories.tsx +894 -0
  283. package/src/components/ui.test.tsx +559 -0
  284. package/src/components/ui.tsx +266 -15
  285. package/src/components/word-count/README.md +240 -0
  286. package/src/hooks.ts +1 -0
  287. package/src/index.ts +51 -19
  288. package/src/sass/_properties.scss +1 -0
  289. package/src/styles/alert/alert.css +94 -4
  290. package/src/styles/alert/alert.css.map +1 -1
  291. package/src/styles/badge/badge.css +20 -2
  292. package/src/styles/badge/badge.css.map +1 -1
  293. package/src/styles/buttons/button.css +31 -31
  294. package/src/styles/buttons/button.css.map +1 -1
  295. package/src/styles/cards/card.css +16 -0
  296. package/src/styles/cards/card.css.map +1 -1
  297. package/src/styles/details/details.css +19 -0
  298. package/src/styles/details/details.css.map +1 -1
  299. package/src/styles/dialog/dialog.css +43 -2
  300. package/src/styles/dialog/dialog.css.map +1 -1
  301. package/src/styles/images/img.css +15 -3
  302. package/src/styles/images/img.css.map +1 -1
  303. package/src/styles/index.css +240 -43
  304. package/src/styles/index.css.map +1 -1
  305. package/src/test/setup.d.ts +9 -0
  306. package/src/test/setup.ts +53 -1
  307. package/libs/chunk-PWVRDQ3R.js +0 -8
  308. package/libs/chunk-PWVRDQ3R.js.map +0 -1
  309. package/libs/chunk-SVS4MX3U.cjs +0 -31
  310. package/libs/chunk-SVS4MX3U.cjs.map +0 -1
  311. package/src/components/cards/README.md +0 -80
  312. package/src/components/dialog/hooks/useClickOutside.ts +0 -33
@@ -0,0 +1,332 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="FP.REACT Components/Icons/Readme" />
4
+
5
+ # Icon Component
6
+
7
+ ## Summary
8
+
9
+ The `Icon` component is an accessibility-first wrapper for SVG icons that enforces WCAG 2.1 Level AA compliance by hiding decorative icons from screen readers by default. It provides a flexible, type-safe API for both decorative and semantic icon usage patterns.
10
+
11
+ ## Features
12
+
13
+ - **Accessibility by Default** - Icons are hidden from screen readers (`aria-hidden="true"`) by default
14
+ - **Decorative vs. Semantic Patterns** - Clear distinction between decorative and meaningful icons
15
+ - **Type-Safe Props** - Full TypeScript support with comprehensive prop definitions
16
+ - **Rich Icon Library** - 30+ pre-built SVG icons (arrows, actions, alerts, media controls)
17
+ - **Customizable Styling** - Control size, color, stroke, and fill properties
18
+ - **Compound Component Pattern** - Access icons via `Icon.Code`, `Icon.Home`, etc.
19
+ - **WCAG 2.1 AA Compliant** - Follows accessibility best practices out of the box
20
+ - **Backward Compatible** - Deprecated `alt` prop maintained for legacy code
21
+
22
+ ## Key Accessibility Principle
23
+
24
+ > **Decorative images must be hidden from assistive technology** (WCAG 1.1.1)
25
+
26
+ Most icons in user interfaces are **decorative** — they accompany visible text labels (e.g., a save icon next to "Save Changes"). Screen readers should not announce these redundantly.
27
+
28
+ When icons are **semantic** (standalone without text), developers must explicitly:
29
+ 1. Set `aria-hidden={false}` to expose the icon
30
+ 2. Provide `aria-label` for an accessible name
31
+
32
+ ## Props
33
+
34
+ ### Icon Wrapper Props
35
+
36
+ | Name | Type | Default | Description |
37
+ | --- | --- | --- | --- |
38
+ | `aria-hidden` | `boolean` | `true` | Controls screen reader visibility. `true` = decorative, `false` = semantic |
39
+ | `aria-label` | `string` | - | Accessible label (required for standalone semantic icons) |
40
+ | `role` | `string` | - | ARIA role override (use `"img"` for complex semantic SVGs) |
41
+ | `styles` | `React.CSSProperties` | - | Inline styles for the wrapper span |
42
+ | `classes` | `string` | - | CSS class names |
43
+ | `children` | `React.ReactNode` | - | Icon SVG component(s) to render |
44
+
45
+ ### Individual Icon Props
46
+
47
+ Props for individual icon components (e.g., `Icon.Code`, `Icon.Home`):
48
+
49
+ | Name | Type | Default | Description |
50
+ | --- | --- | --- | --- |
51
+ | `size` | `number` | `16` | Icon size in pixels (sets both width and height) |
52
+ | `fill` | `string` | `'none'` | SVG fill color (CSS color value) |
53
+ | `strokeColor` | `string` | `'currentColor'` | SVG stroke color (CSS color value) |
54
+ | `strokeWidth` | `string` | `'2'` | SVG stroke width |
55
+ | `width` | `number` | - | Explicit width in pixels (overrides size) |
56
+ | `height` | `number` | - | Explicit height in pixels (overrides size) |
57
+ | `alt` | `string` | - | **@deprecated** Use `aria-label` instead |
58
+
59
+ ## Available Icons
60
+
61
+ ### Action Icons
62
+ - `Icon.Add`, `Icon.Minus`, `Icon.Remove` (Close), `Icon.Copy`, `Icon.Star`
63
+
64
+ ### Navigation Icons
65
+ - `Icon.ArrowUp`, `Icon.ArrowDown`, `Icon.ArrowLeft`, `Icon.ArrowRight`
66
+ - `Icon.Up`, `Icon.Down`, `Icon.Left`, `Icon.Right`
67
+ - `Icon.Home`
68
+
69
+ ### Media Control Icons
70
+ - `Icon.Play`, `Icon.Pause`, `Icon.Stop`, `Icon.Resume`
71
+ - `Icon.PlaySolid`, `Icon.PauseSolid`, `Icon.StopSolid`, `Icon.ResumeSolid`
72
+
73
+ ### Status/Alert Icons
74
+ - `Icon.Info`, `Icon.InfoSolid`
75
+ - `Icon.AlertSolid`, `Icon.AlertSquareSolid`
76
+ - `Icon.WarnSolid`, `Icon.SuccessSolid`, `Icon.QuestionSolid`
77
+
78
+ ### Other Icons
79
+ - `Icon.Code`, `Icon.Chat`, `Icon.User`
80
+
81
+ ### Aliases
82
+ - `Icon.Close` → `Icon.Remove`
83
+
84
+ ## Usage Examples
85
+
86
+ ### ✅ Decorative Icon with Text (Default, Recommended)
87
+
88
+ When icons accompany visible text, they are decorative and should be hidden from screen readers:
89
+
90
+ ```tsx
91
+ import { Button } from "@fpkit/acss";
92
+ import { Icon } from "@fpkit/acss";
93
+
94
+ // Screen reader announces: "Save Changes, button"
95
+ const SaveButton = () => (
96
+ <Button type="button" onClick={handleSave}>
97
+ <Icon>
98
+ <Icon.Code />
99
+ </Icon>
100
+ Save Changes
101
+ </Button>
102
+ );
103
+ ```
104
+
105
+ **Why this works:**
106
+ - Icon is decorative (accompanies "Save Changes" text)
107
+ - `aria-hidden="true"` is applied by default
108
+ - Screen reader users hear only "Save Changes, button" (no redundancy)
109
+
110
+ ---
111
+
112
+ ### ✅ Semantic Icon-Only Button (Explicit Pattern)
113
+
114
+ When icons stand alone without visible text, they must be exposed to screen readers:
115
+
116
+ ```tsx
117
+ import { Button } from "@fpkit/acss";
118
+ import { Icon } from "@fpkit/acss";
119
+
120
+ // Screen reader announces: "Close dialog, button"
121
+ const CloseButton = () => (
122
+ <Button type="button" aria-label="Close dialog" onClick={handleClose}>
123
+ <Icon aria-hidden={false}>
124
+ <Icon.Remove />
125
+ </Icon>
126
+ </Button>
127
+ );
128
+ ```
129
+
130
+ **Why this works:**
131
+ - Icon is semantic (no visible text)
132
+ - `aria-hidden={false}` exposes icon to screen readers
133
+ - `aria-label` on the button provides accessible name
134
+ - Meets WCAG 4.1.2 (Name, Role, Value)
135
+
136
+ ---
137
+
138
+ ### ✅ Semantic Icon with role="img"
139
+
140
+ For complex SVG icons that convey meaning, use `role="img"`:
141
+
142
+ ```tsx
143
+ import { Icon } from "@fpkit/acss";
144
+
145
+ // Screen reader announces: "Code snippet, image"
146
+ const CodeIcon = () => (
147
+ <Icon
148
+ aria-hidden={false}
149
+ aria-label="Code snippet"
150
+ role="img"
151
+ >
152
+ <Icon.Code size={32} />
153
+ </Icon>
154
+ );
155
+ ```
156
+
157
+ **Why this works:**
158
+ - `role="img"` tells screen readers this is a meaningful image
159
+ - `aria-label` provides the image description
160
+ - Follows WCAG 1.1.1 (Non-text Content)
161
+
162
+ ---
163
+
164
+ ### ✅ Custom Styling
165
+
166
+ Icons inherit text color by default (`strokeColor="currentColor"`):
167
+
168
+ ```tsx
169
+ import { Icon } from "@fpkit/acss";
170
+
171
+ const StyledIcon = () => (
172
+ <div style={{ color: "blue" }}>
173
+ <Icon>
174
+ <Icon.Star size={24} fill="gold" strokeColor="orange" />
175
+ </Icon>
176
+ Favorite
177
+ </div>
178
+ );
179
+ ```
180
+
181
+ ---
182
+
183
+ ### ❌ Anti-Pattern: Icon-Only Button Without Label
184
+
185
+ **This violates WCAG 4.1.2 (Name, Role, Value):**
186
+
187
+ ```tsx
188
+ // ❌ BAD: Screen reader cannot identify button purpose
189
+ const BadButton = () => (
190
+ <Button type="button" onClick={handleClose}>
191
+ <Icon>
192
+ <Icon.Remove />
193
+ </Icon>
194
+ </Button>
195
+ );
196
+ ```
197
+
198
+ **Why this fails:**
199
+ - Icon-only button with no accessible name
200
+ - Screen reader announces only "button" (no context)
201
+ - Users cannot determine what the button does
202
+
203
+ **Fix:**
204
+ ```tsx
205
+ // ✅ GOOD: Add aria-label to button
206
+ <Button type="button" aria-label="Close" onClick={handleClose}>
207
+ <Icon aria-hidden={false}>
208
+ <Icon.Remove />
209
+ </Icon>
210
+ </Button>
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Technical Details
216
+
217
+ ### Component Architecture
218
+
219
+ The Icon component uses a **compound component pattern**:
220
+ - `Icon` - Wrapper component handling accessibility
221
+ - `Icon.Code`, `Icon.Home`, etc. - Individual SVG icon components
222
+ - `Svg` - Base SVG component used by all icons
223
+
224
+ ### Polymorphic UI Component
225
+
226
+ The Icon wrapper uses the polymorphic `UI` component from `#components/ui`, which:
227
+ - Supports ref forwarding for focus management
228
+ - Accepts all native HTML span attributes
229
+ - Merges default and custom styles
230
+
231
+ ### Type Safety
232
+
233
+ The component is fully typed with TypeScript:
234
+
235
+ ```tsx
236
+ export type IconProps = React.ComponentProps<typeof UI> & {
237
+ "aria-hidden"?: boolean;
238
+ "aria-label"?: string;
239
+ role?: string;
240
+ };
241
+ ```
242
+
243
+ Individual icon props extend `IconProps` from `types.ts` with visual styling properties.
244
+
245
+ ---
246
+
247
+ ## Accessibility Checklist
248
+
249
+ When using icons, ensure:
250
+
251
+ - [ ] **Decorative icons** (with text) use default `aria-hidden="true"`
252
+ - [ ] **Semantic icons** (standalone) have `aria-hidden={false}`
253
+ - [ ] **Semantic icons** have `aria-label` or `aria-labelledby`
254
+ - [ ] **Icon-only buttons** have `aria-label` on the button element
255
+ - [ ] **Icon color contrast** meets WCAG 1.4.11 (3:1 for UI components)
256
+ - [ ] **Touch targets** are at least 44×44 CSS pixels (WCAG 2.5.5)
257
+
258
+ ---
259
+
260
+ ## Migration Guide
261
+
262
+ ### Deprecation: `alt` Prop
263
+
264
+ The `alt` prop is deprecated because it's only valid for `<img>` elements, not SVG.
265
+
266
+ **Before:**
267
+ ```tsx
268
+ <Icon.Code alt="Code icon" />
269
+ ```
270
+
271
+ **After:**
272
+ ```tsx
273
+ <Icon aria-hidden={false} aria-label="Code icon">
274
+ <Icon.Code />
275
+ </Icon>
276
+ ```
277
+
278
+ ### Adding aria-hidden to Existing Icons
279
+
280
+ If you have existing icon usage without explicit accessibility attributes:
281
+
282
+ **Before:**
283
+ ```tsx
284
+ <button>
285
+ <Icon><Icon.Remove /></Icon>
286
+ Close
287
+ </button>
288
+ ```
289
+
290
+ **After (no change needed):**
291
+ ```tsx
292
+ // Icons are now hidden by default - existing code is more accessible!
293
+ <button>
294
+ <Icon><Icon.Remove /></Icon>
295
+ Close
296
+ </button>
297
+ ```
298
+
299
+ For icon-only buttons, update to:
300
+
301
+ ```tsx
302
+ <button aria-label="Close">
303
+ <Icon aria-hidden={false}>
304
+ <Icon.Remove />
305
+ </Icon>
306
+ </button>
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Additional Notes
312
+
313
+ - **Color inheritance**: Icons use `strokeColor="currentColor"` by default, inheriting parent text color
314
+ - **Size units**: Icon sizes are in pixels (e.g., `size={24}` = 24px)
315
+ - **Performance**: SVG icons are lightweight and render efficiently
316
+ - **Bundle size**: Icons are tree-shakeable when using named imports
317
+ - **Browser support**: Works in all modern browsers supporting SVG
318
+
319
+ ---
320
+
321
+ ## Related Components
322
+
323
+ - [Button](/docs/fp-react-components-buttons-readme--docs) - Learn about accessible button patterns
324
+ - [Alert](/docs/fp-react-components-alert-readme--docs) - Uses icons for severity indicators
325
+
326
+ ---
327
+
328
+ ## Further Reading
329
+
330
+ - [WCAG 1.1.1: Non-text Content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html)
331
+ - [WCAG 4.1.2: Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html)
332
+ - [Accessible Icon Buttons (Sara Soueidan)](https://www.sarasoueidan.com/blog/accessible-icon-buttons/)
@@ -53,9 +53,14 @@ export const IconButton = {
53
53
  },
54
54
  };
55
55
 
56
+ /**
57
+ * Decorative icon (default behavior).
58
+ * Icon is hidden from screen readers via aria-hidden="true".
59
+ * Use when icon accompanies visible text.
60
+ */
56
61
  export const Code: Story = {
57
62
  args: {
58
- children: <Icon.Code role="img" aria-label="code icon" />,
63
+ children: <Icon.Code />,
59
64
  },
60
65
  } as Story;
61
66
 
@@ -249,3 +254,71 @@ export const AlertSquareSolid: Story = {
249
254
  children: <Icon.AlertSquareSolid />,
250
255
  },
251
256
  } as Story;
257
+
258
+ /**
259
+ * ✅ ACCESSIBLE PATTERN: Decorative icon with visible text.
260
+ * Icon is hidden from screen readers (default aria-hidden="true").
261
+ * Screen readers announce only "Save Changes".
262
+ */
263
+ export const DecorativeIconWithText: Story = {
264
+ args: {},
265
+ render: () => (
266
+ <Button type="button">
267
+ <Icon>
268
+ <Icon.Code />
269
+ </Icon>
270
+ Save Changes
271
+ </Button>
272
+ ),
273
+ } as Story;
274
+
275
+ /**
276
+ * ✅ ACCESSIBLE PATTERN: Semantic icon-only button.
277
+ * Icon is exposed to screen readers with aria-hidden={false}.
278
+ * aria-label provides accessible name for screen readers.
279
+ */
280
+ export const SemanticIconOnlyButton: Story = {
281
+ args: {},
282
+ render: () => (
283
+ <Button type="button" aria-label="Close dialog">
284
+ <Icon aria-hidden={false}>
285
+ <Icon.Remove />
286
+ </Icon>
287
+ </Button>
288
+ ),
289
+ } as Story;
290
+
291
+ /**
292
+ * ✅ ACCESSIBLE PATTERN: Icon with role="img" for semantic meaning.
293
+ * Use when icon conveys information beyond decoration.
294
+ * Requires aria-label for accessible name.
295
+ */
296
+ export const SemanticIconWithRole: Story = {
297
+ args: {
298
+ "aria-hidden": false,
299
+ "aria-label": "Code snippet",
300
+ role: "img",
301
+ children: <Icon.Code />,
302
+ },
303
+ } as Story;
304
+
305
+ /**
306
+ * ❌ ANTI-PATTERN: Icon-only button without accessible name.
307
+ * This will fail WCAG 4.1.2 (Name, Role, Value).
308
+ * Screen readers cannot identify the button's purpose.
309
+ */
310
+ export const IconOnlyButtonNoLabel: Story = {
311
+ args: {},
312
+ render: () => (
313
+ <div style={{ opacity: 0.5, border: "2px dashed red", padding: "1rem" }}>
314
+ <p style={{ color: "red", marginBottom: "0.5rem" }}>
315
+ ❌ BAD: No accessible name
316
+ </p>
317
+ <Button type="button">
318
+ <Icon>
319
+ <Icon.Remove />
320
+ </Icon>
321
+ </Button>
322
+ </div>
323
+ ),
324
+ } as Story;
@@ -53,13 +53,98 @@ const defaultStyles = {
53
53
  width: "auto",
54
54
  };
55
55
 
56
- export type IconProps = React.ComponentProps<typeof UI>;
56
+ export type IconProps = React.ComponentProps<typeof UI> & {
57
+ /**
58
+ * Controls screen reader visibility.
59
+ * - `true` (default): Hides icon from screen readers (decorative icon)
60
+ * - `false`: Makes icon visible to screen readers (semantic icon)
61
+ *
62
+ * **When to use decorative (aria-hidden="true", default):**
63
+ * - Icon accompanies visible text (e.g., button with icon + label)
64
+ * - Icon is purely visual decoration
65
+ *
66
+ * **When to use semantic (aria-hidden="false"):**
67
+ * - Icon is the only content (e.g., icon-only button)
68
+ * - Must provide `aria-label` or `aria-labelledby` for accessible name
69
+ *
70
+ * @default true
71
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html
72
+ */
73
+ "aria-hidden"?: boolean;
74
+ /**
75
+ * Accessible label for semantic icons.
76
+ * Required when icon is standalone (not accompanied by visible text).
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * // ✅ GOOD: Icon-only button with accessible name
81
+ * <button>
82
+ * <Icon aria-hidden={false} aria-label="Close dialog">
83
+ * <Icon.Remove />
84
+ * </Icon>
85
+ * </button>
86
+ *
87
+ * // ✅ GOOD: Icon with visible text (default decorative)
88
+ * <button>
89
+ * <Icon><Icon.Add /></Icon>
90
+ * Add Item
91
+ * </button>
92
+ * ```
93
+ */
94
+ "aria-label"?: string;
95
+ /**
96
+ * Semantic role override.
97
+ * Use `role="img"` when icon conveys meaning and is not decorative.
98
+ *
99
+ * @default undefined (no role for decorative icons)
100
+ */
101
+ role?: string;
102
+ };
57
103
 
104
+ /**
105
+ * Icon wrapper component that enforces accessibility best practices.
106
+ *
107
+ * **Default behavior (decorative icon):**
108
+ * - Hidden from screen readers (`aria-hidden="true"`)
109
+ * - Used when icon accompanies visible text
110
+ *
111
+ * **Semantic icon pattern:**
112
+ * - Set `aria-hidden={false}` to expose to screen readers
113
+ * - Must provide `aria-label` for accessible name
114
+ * - Consider `role="img"` for complex SVG icons
115
+ *
116
+ * @param {IconProps} props - Component props
117
+ * @returns {React.ReactElement} Accessible icon wrapper
118
+ *
119
+ * @example
120
+ * // ✅ Decorative icon with text (default)
121
+ * <button>
122
+ * <Icon><Icon.Save /></Icon>
123
+ * Save Changes
124
+ * </button>
125
+ *
126
+ * @example
127
+ * // ✅ Semantic icon-only button
128
+ * <button aria-label="Close dialog">
129
+ * <Icon aria-hidden={false}>
130
+ * <Icon.Remove />
131
+ * </Icon>
132
+ * </button>
133
+ *
134
+ * @example
135
+ * // ❌ BAD: Icon-only button without accessible name
136
+ * <button>
137
+ * <Icon><Icon.Remove /></Icon>
138
+ * </button>
139
+ */
58
140
  export const Icon = ({
59
141
  id,
60
142
  classes,
61
143
  children,
62
144
  styles,
145
+ "aria-hidden": ariaHidden = true,
146
+ "aria-label": ariaLabel,
147
+ role,
63
148
  ...props
64
149
  }: IconProps) => {
65
150
  return (
@@ -70,6 +155,9 @@ export const Icon = ({
70
155
  className={classes}
71
156
  data-icon
72
157
  data-style="icons"
158
+ aria-hidden={ariaHidden}
159
+ aria-label={ariaLabel}
160
+ role={role}
73
161
  {...props}
74
162
  >
75
163
  {children}
@@ -1,12 +1,59 @@
1
1
  import { ComponentProps } from '#/types'
2
2
 
3
+ /**
4
+ * Props for individual icon SVG components (e.g., Icon.Code, Icon.Home).
5
+ *
6
+ * These props control the visual presentation of SVG icons. Icon components
7
+ * are decorative by default and should be wrapped in the `Icon` container
8
+ * component which handles accessibility concerns.
9
+ *
10
+ * @property {string} [fill] - SVG fill color (CSS color value)
11
+ * @property {number} [size] - Icon size in pixels (sets both width and height)
12
+ * @property {string} [strokeColor] - SVG stroke color (CSS color value)
13
+ * @property {string} [strokeWidth] - SVG stroke width (e.g., '2px', '1.5')
14
+ * @property {string} [role] - ARIA role (use 'img' for semantic icons)
15
+ * @property {string} [aria-label] - Accessible label for standalone icons
16
+ * @property {boolean} [aria-hidden] - Hide from screen readers (default: true via Icon wrapper)
17
+ * @property {string} [alt] - @deprecated Use aria-label instead. Legacy prop for accessible label.
18
+ * @property {number} [width] - Explicit width in pixels (overrides size)
19
+ * @property {number} [height] - Explicit height in pixels (overrides size)
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Decorative icon with custom styling
24
+ * <Icon>
25
+ * <Icon.Code size={24} fill="blue" />
26
+ * View Code
27
+ * </Icon>
28
+ *
29
+ * // Standalone semantic icon
30
+ * <Icon aria-hidden={false} aria-label="Code snippet">
31
+ * <Icon.Code size={20} />
32
+ * </Icon>
33
+ * ```
34
+ */
3
35
  export interface IconProps extends Partial<ComponentProps> {
36
+ /** SVG fill color */
4
37
  fill?: string
38
+ /** Icon size in pixels (sets both width and height) */
5
39
  size?: number
40
+ /** SVG stroke color */
6
41
  strokeColor?: string
42
+ /** SVG stroke width */
7
43
  strokeWidth?: string
44
+ /** ARIA role (use 'img' for semantic icons) */
8
45
  role?: string
46
+ /** Accessible label (required for standalone icons) */
47
+ 'aria-label'?: string
48
+ /** Hide from screen readers (default: true) */
49
+ 'aria-hidden'?: boolean
50
+ /**
51
+ * @deprecated Use aria-label instead. This prop exists for backward compatibility.
52
+ * The alt attribute is only valid for img elements, not SVG.
53
+ */
9
54
  alt?: string
55
+ /** Explicit width in pixels */
10
56
  width?: number
57
+ /** Explicit height in pixels */
11
58
  height?: number
12
59
  }