@boostdev/design-system-components 2.14.1 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/AGENTS.md +20 -15
  2. package/dist/client.cjs +271 -205
  3. package/dist/client.css +808 -648
  4. package/dist/client.d.cts +19 -4
  5. package/dist/client.d.ts +19 -4
  6. package/dist/client.js +206 -139
  7. package/dist/index.cjs +271 -205
  8. package/dist/index.css +808 -648
  9. package/dist/index.d.cts +19 -4
  10. package/dist/index.d.ts +19 -4
  11. package/dist/index.js +206 -139
  12. package/dist/web-components/chunk-2DHW2WJZ.js +154 -0
  13. package/dist/web-components/{chunk-LDIPCTRN.js → chunk-2MJTO2IW.js} +48 -2
  14. package/dist/web-components/{chunk-3WAV6MDN.js → chunk-3FSGP35V.js} +1 -0
  15. package/dist/web-components/{chunk-KA3VPKMY.js → chunk-4MB2ICSK.js} +1 -0
  16. package/dist/web-components/{chunk-IDSM74LM.js → chunk-6XL5P76D.js} +3 -3
  17. package/dist/web-components/{chunk-P24JYC5V.js → chunk-7VXRMCAL.js} +10 -4
  18. package/dist/web-components/{chunk-CUKQ2L5B.js → chunk-AGI6YQRC.js} +9 -16
  19. package/dist/web-components/chunk-BDRV452N.js +51 -0
  20. package/dist/web-components/{chunk-3AY2YKCM.js → chunk-CKCPV347.js} +1 -0
  21. package/dist/web-components/{chunk-HBLJ4SLY.js → chunk-DD3OUXNG.js} +5 -5
  22. package/dist/web-components/{chunk-RVF7W653.js → chunk-DGOCPKHI.js} +30 -10
  23. package/dist/web-components/{chunk-2OLVQUBU.js → chunk-EYCW7TKB.js} +8 -15
  24. package/dist/web-components/{chunk-VANAKB6D.js → chunk-GPJWLAKU.js} +33 -16
  25. package/dist/web-components/{chunk-VED2DFJ6.js → chunk-GQNQBEYR.js} +8 -15
  26. package/dist/web-components/{chunk-WFVKU4GX.js → chunk-IKAJLROF.js} +49 -4
  27. package/dist/web-components/{chunk-RSZ3BWBK.js → chunk-KSK4RZZQ.js} +30 -7
  28. package/dist/web-components/{chunk-FEDABOR5.js → chunk-LHFUXZ52.js} +7 -15
  29. package/dist/web-components/{chunk-M3ECULRH.js → chunk-RBF7WPLI.js} +10 -8
  30. package/dist/web-components/{chunk-6G22I2CK.js → chunk-RZH5IVB5.js} +31 -1
  31. package/dist/web-components/chunk-SRKQ6GST.js +40 -0
  32. package/dist/web-components/{chunk-5HUAFCU6.js → chunk-TJV7UIXC.js} +7 -0
  33. package/dist/web-components/{chunk-J3HVUIUH.js → chunk-XI2U6U26.js} +8 -15
  34. package/dist/web-components/{chunk-JH6LQKLK.js → chunk-XJU7KITO.js} +8 -15
  35. package/dist/web-components/{chunk-XIZWAWMJ.js → chunk-XTPUKUN5.js} +16 -6
  36. package/dist/web-components/{chunk-JV5QT2OU.js → chunk-YLDQOESI.js} +27 -13
  37. package/dist/web-components/globals.js +1 -1
  38. package/dist/web-components/index.d.ts +1 -0
  39. package/dist/web-components/index.js +34 -29
  40. package/dist/web-components/interaction/bds-button.js +1 -1
  41. package/dist/web-components/interaction/bds-dropdown-menu-group.d.ts +41 -0
  42. package/dist/web-components/interaction/bds-dropdown-menu-group.js +7 -0
  43. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +14 -1
  44. package/dist/web-components/interaction/bds-dropdown-menu-item.js +1 -1
  45. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +2 -1
  46. package/dist/web-components/interaction/bds-dropdown-menu.js +1 -1
  47. package/dist/web-components/interaction/bds-tab.d.ts +5 -0
  48. package/dist/web-components/interaction/bds-tab.js +1 -1
  49. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  50. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +6 -0
  51. package/dist/web-components/interaction/form/bds-checkbox-group.js +2 -1
  52. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  53. package/dist/web-components/interaction/form/bds-combobox.js +2 -1
  54. package/dist/web-components/interaction/form/bds-form-input.js +2 -1
  55. package/dist/web-components/interaction/form/bds-number-input.js +2 -1
  56. package/dist/web-components/interaction/form/bds-radio-group.d.ts +5 -0
  57. package/dist/web-components/interaction/form/bds-radio-group.js +2 -1
  58. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  59. package/dist/web-components/interaction/form/bds-select.js +2 -1
  60. package/dist/web-components/interaction/form/bds-slider.js +2 -1
  61. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  62. package/dist/web-components/interaction/form/bds-textarea.js +2 -1
  63. package/dist/web-components/ui/bds-alert.d.ts +8 -1
  64. package/dist/web-components/ui/bds-alert.js +1 -1
  65. package/dist/web-components/ui/bds-avatar.d.ts +11 -0
  66. package/dist/web-components/ui/bds-avatar.js +1 -1
  67. package/dist/web-components/ui/bds-notification-banner.d.ts +14 -1
  68. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  69. package/dist/web-components/ui/bds-section-header.d.ts +6 -0
  70. package/dist/web-components/ui/bds-section-header.js +1 -1
  71. package/dist/web-components/ui/bds-separator.js +1 -1
  72. package/dist/web-components/ui/bds-table.js +1 -1
  73. package/package.json +6 -2
  74. package/src/components/interaction/Button/Button.mdx +7 -0
  75. package/src/components/interaction/Button/Button.module.css +17 -9
  76. package/src/components/interaction/Button/Button.stories.tsx +28 -0
  77. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +35 -4
  78. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +58 -10
  79. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +114 -1
  80. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +25 -0
  81. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +69 -17
  82. package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -0
  83. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +7 -0
  84. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +1 -0
  85. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +7 -0
  86. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +22 -0
  87. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +1 -1
  88. package/src/components/interaction/form/Combobox/Combobox.module.css +1 -0
  89. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +7 -0
  90. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -0
  91. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -0
  92. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +7 -0
  93. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -0
  94. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +7 -0
  95. package/src/components/interaction/form/Radio/Radio.module.css +1 -0
  96. package/src/components/interaction/form/Radio/Radio.spec.tsx +7 -0
  97. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +1 -0
  98. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +7 -0
  99. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +22 -0
  100. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +1 -1
  101. package/src/components/interaction/form/Select/Select.module.css +1 -0
  102. package/src/components/interaction/form/Select/Select.spec.tsx +7 -0
  103. package/src/components/interaction/form/Switch/Switch.module.css +1 -0
  104. package/src/components/interaction/form/Switch/Switch.spec.tsx +7 -0
  105. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -0
  106. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +7 -0
  107. package/src/components/interaction/form/atoms/Message.module.css +14 -3
  108. package/src/components/interaction/form/atoms/Message.spec.tsx +22 -0
  109. package/src/components/interaction/form/atoms/Message.tsx +5 -0
  110. package/src/components/layout/SectionHeader/SectionHeader.mdx +18 -5
  111. package/src/components/layout/SectionHeader/SectionHeader.module.css +35 -14
  112. package/src/components/layout/SectionHeader/SectionHeader.spec.tsx +44 -0
  113. package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +5 -0
  114. package/src/components/layout/SectionHeader/SectionHeader.tsx +3 -1
  115. package/src/components/ui/Alert/Alert.mdx +11 -0
  116. package/src/components/ui/Alert/Alert.module.css +30 -1
  117. package/src/components/ui/Alert/Alert.spec.tsx +21 -0
  118. package/src/components/ui/Alert/Alert.stories.tsx +20 -0
  119. package/src/components/ui/Alert/Alert.tsx +8 -1
  120. package/src/components/ui/Avatar/Avatar.mdx +22 -2
  121. package/src/components/ui/Avatar/Avatar.module.css +29 -2
  122. package/src/components/ui/Avatar/Avatar.spec.tsx +26 -0
  123. package/src/components/ui/Avatar/Avatar.stories.tsx +23 -0
  124. package/src/components/ui/Avatar/Avatar.tsx +4 -2
  125. package/src/components/ui/Avatar/avatarTone.spec.ts +41 -0
  126. package/src/components/ui/Avatar/avatarTone.ts +22 -0
  127. package/src/components/ui/Collapsible/Collapsible.module.css +1 -2
  128. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +14 -0
  129. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +31 -1
  130. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +29 -0
  131. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +25 -0
  132. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +15 -1
  133. package/src/components/ui/Separator/Separator.mdx +1 -0
  134. package/src/components/ui/Separator/Separator.module.css +6 -0
  135. package/src/components/ui/Separator/Separator.spec.tsx +19 -0
  136. package/src/components/ui/Separator/Separator.stories.tsx +15 -0
  137. package/src/components/ui/Table/Table.mdx +4 -1
  138. package/src/components/ui/Table/Table.module.css +7 -7
  139. package/src/components/ui/Table/Table.spec.tsx +11 -0
  140. package/src/components/ui/Tabs/Tabs.mdx +11 -5
  141. package/src/components/ui/Tabs/Tabs.module.css +23 -5
  142. package/src/components/ui/Tabs/Tabs.spec.tsx +39 -0
  143. package/src/components/ui/Tabs/Tabs.tsx +2 -1
  144. package/src/components/ui/Tabs/getNodeText.spec.tsx +23 -0
  145. package/src/components/ui/Tabs/getNodeText.ts +13 -0
  146. package/src/components/ui/Tooltip/Tooltip.mdx +6 -3
  147. package/src/components/ui/Tooltip/Tooltip.module.css +6 -2
  148. package/src/components/ui/Tooltip/Tooltip.spec.tsx +10 -0
  149. package/src/components/ui/Tooltip/Tooltip.stories.tsx +13 -0
  150. package/src/css/bdc.css +5 -2
  151. package/src/css/bdc.spec.ts +65 -0
  152. package/src/stories/DesignSystem/Borders.mdx +17 -9
  153. package/src/web-components/globals.ts +10 -4
  154. package/src/web-components/index.ts +1 -0
  155. package/src/web-components/interaction/BdsAccordion.mdx +1 -1
  156. package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
  157. package/src/web-components/interaction/BdsButton.mdx +9 -1
  158. package/src/web-components/interaction/BdsButton.stories.tsx +42 -1
  159. package/src/web-components/interaction/BdsCollapsible.mdx +1 -1
  160. package/src/web-components/interaction/BdsCollapsible.stories.tsx +1 -1
  161. package/src/web-components/interaction/BdsDialog.mdx +1 -1
  162. package/src/web-components/interaction/BdsDialog.stories.tsx +1 -1
  163. package/src/web-components/interaction/BdsDrawer.mdx +1 -1
  164. package/src/web-components/interaction/BdsDrawer.stories.tsx +1 -1
  165. package/src/web-components/interaction/BdsDropdownMenu.mdx +62 -6
  166. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +32 -7
  167. package/src/web-components/interaction/BdsPopover.mdx +1 -1
  168. package/src/web-components/interaction/BdsPopover.stories.tsx +1 -1
  169. package/src/web-components/interaction/BdsSkipLink.mdx +1 -1
  170. package/src/web-components/interaction/BdsSkipLink.stories.tsx +1 -1
  171. package/src/web-components/interaction/BdsTabs.mdx +10 -1
  172. package/src/web-components/interaction/BdsTabs.stories.tsx +1 -1
  173. package/src/web-components/interaction/BdsToastProvider.mdx +1 -1
  174. package/src/web-components/interaction/BdsToastProvider.stories.tsx +1 -1
  175. package/src/web-components/interaction/BdsTooltip.mdx +13 -5
  176. package/src/web-components/interaction/BdsTooltip.stories.tsx +12 -12
  177. package/src/web-components/interaction/bds-button.ts +16 -6
  178. package/src/web-components/interaction/bds-dropdown-menu-group.ts +72 -0
  179. package/src/web-components/interaction/bds-dropdown-menu-item.ts +65 -17
  180. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +135 -0
  181. package/src/web-components/interaction/bds-dropdown-menu.ts +5 -4
  182. package/src/web-components/interaction/bds-tab.spec.ts +34 -2
  183. package/src/web-components/interaction/bds-tab.ts +31 -6
  184. package/src/web-components/interaction/bds-tooltip.spec.ts +16 -0
  185. package/src/web-components/interaction/bds-tooltip.ts +10 -8
  186. package/src/web-components/interaction/form/BdsCheckbox.mdx +5 -1
  187. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +1 -1
  188. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +5 -1
  189. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +1 -1
  190. package/src/web-components/interaction/form/BdsCombobox.mdx +5 -1
  191. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +1 -1
  192. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  193. package/src/web-components/interaction/form/BdsFileInput.mdx +1 -1
  194. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +1 -1
  195. package/src/web-components/interaction/form/BdsFormInput.mdx +8 -1
  196. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +1 -1
  197. package/src/web-components/interaction/form/BdsNumberInput.mdx +5 -1
  198. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +1 -1
  199. package/src/web-components/interaction/form/BdsRadio.mdx +5 -1
  200. package/src/web-components/interaction/form/BdsRadio.stories.tsx +1 -1
  201. package/src/web-components/interaction/form/BdsRadioGroup.mdx +5 -1
  202. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +1 -1
  203. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +1 -1
  204. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +1 -1
  205. package/src/web-components/interaction/form/BdsSelect.mdx +5 -1
  206. package/src/web-components/interaction/form/BdsSelect.stories.tsx +1 -1
  207. package/src/web-components/interaction/form/BdsSlider.mdx +5 -1
  208. package/src/web-components/interaction/form/BdsSlider.stories.tsx +1 -1
  209. package/src/web-components/interaction/form/BdsSwitch.mdx +5 -1
  210. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +1 -1
  211. package/src/web-components/interaction/form/BdsTextarea.mdx +5 -1
  212. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +1 -1
  213. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +32 -3
  214. package/src/web-components/interaction/form/bds-checkbox-group.ts +33 -17
  215. package/src/web-components/interaction/form/bds-checkbox.ts +1 -0
  216. package/src/web-components/interaction/form/bds-combobox.ts +6 -16
  217. package/src/web-components/interaction/form/bds-form-input.ts +6 -16
  218. package/src/web-components/interaction/form/bds-number-input.ts +6 -16
  219. package/src/web-components/interaction/form/bds-radio-group.spec.ts +32 -3
  220. package/src/web-components/interaction/form/bds-radio-group.ts +26 -14
  221. package/src/web-components/interaction/form/bds-radio.ts +1 -0
  222. package/src/web-components/interaction/form/bds-select.ts +6 -16
  223. package/src/web-components/interaction/form/bds-slider.ts +5 -16
  224. package/src/web-components/interaction/form/bds-switch.ts +1 -0
  225. package/src/web-components/interaction/form/bds-textarea.ts +6 -16
  226. package/src/web-components/interaction/form/field-message.spec.ts +62 -0
  227. package/src/web-components/interaction/form/field-message.ts +55 -0
  228. package/src/web-components/layout/BdsGrid.mdx +1 -1
  229. package/src/web-components/layout/BdsGrid.stories.tsx +1 -1
  230. package/src/web-components/layout/BdsGridItem.mdx +1 -1
  231. package/src/web-components/layout/BdsGridItem.stories.tsx +1 -1
  232. package/src/web-components/ui/BdsAlert.mdx +19 -1
  233. package/src/web-components/ui/BdsAlert.stories.tsx +29 -3
  234. package/src/web-components/ui/BdsAvatar.mdx +20 -3
  235. package/src/web-components/ui/BdsAvatar.stories.tsx +25 -2
  236. package/src/web-components/ui/BdsBadge.mdx +1 -1
  237. package/src/web-components/ui/BdsBadge.stories.tsx +1 -1
  238. package/src/web-components/ui/BdsBreadcrumb.mdx +1 -1
  239. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +1 -1
  240. package/src/web-components/ui/BdsButtonContainer.mdx +1 -1
  241. package/src/web-components/ui/BdsButtonContainer.stories.tsx +1 -1
  242. package/src/web-components/ui/BdsCalendar.mdx +1 -1
  243. package/src/web-components/ui/BdsCalendar.stories.tsx +1 -1
  244. package/src/web-components/ui/BdsCalendarRange.mdx +1 -1
  245. package/src/web-components/ui/BdsCalendarRange.stories.tsx +1 -1
  246. package/src/web-components/ui/BdsCard.mdx +1 -1
  247. package/src/web-components/ui/BdsCard.stories.tsx +1 -1
  248. package/src/web-components/ui/BdsCarousel.mdx +1 -1
  249. package/src/web-components/ui/BdsCarousel.stories.tsx +1 -1
  250. package/src/web-components/ui/BdsDescriptionList.mdx +1 -1
  251. package/src/web-components/ui/BdsDescriptionList.stories.tsx +1 -1
  252. package/src/web-components/ui/BdsIconWrapper.mdx +1 -1
  253. package/src/web-components/ui/BdsIconWrapper.stories.tsx +1 -1
  254. package/src/web-components/ui/BdsLink.mdx +1 -1
  255. package/src/web-components/ui/BdsLink.stories.tsx +1 -1
  256. package/src/web-components/ui/BdsLoading.mdx +1 -1
  257. package/src/web-components/ui/BdsLoading.stories.tsx +1 -1
  258. package/src/web-components/ui/BdsNotificationBanner.mdx +10 -1
  259. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +29 -2
  260. package/src/web-components/ui/BdsPagination.mdx +1 -1
  261. package/src/web-components/ui/BdsPagination.stories.tsx +1 -1
  262. package/src/web-components/ui/BdsProgress.mdx +1 -1
  263. package/src/web-components/ui/BdsProgress.stories.tsx +1 -1
  264. package/src/web-components/ui/BdsProgressCircle.mdx +1 -1
  265. package/src/web-components/ui/BdsProgressCircle.stories.tsx +1 -1
  266. package/src/web-components/ui/BdsRating.mdx +1 -1
  267. package/src/web-components/ui/BdsRating.stories.tsx +1 -1
  268. package/src/web-components/ui/BdsSectionHeader.mdx +18 -6
  269. package/src/web-components/ui/BdsSectionHeader.stories.tsx +10 -2
  270. package/src/web-components/ui/BdsSeparator.mdx +2 -1
  271. package/src/web-components/ui/BdsSeparator.stories.tsx +13 -1
  272. package/src/web-components/ui/BdsSkeleton.mdx +1 -1
  273. package/src/web-components/ui/BdsSkeleton.stories.tsx +1 -1
  274. package/src/web-components/ui/BdsTable.mdx +5 -1
  275. package/src/web-components/ui/BdsTable.stories.tsx +1 -1
  276. package/src/web-components/ui/BdsTypography.mdx +1 -1
  277. package/src/web-components/ui/BdsTypography.stories.tsx +1 -1
  278. package/src/web-components/ui/bds-alert.spec.ts +27 -0
  279. package/src/web-components/ui/bds-alert.ts +34 -1
  280. package/src/web-components/ui/bds-avatar.spec.ts +47 -0
  281. package/src/web-components/ui/bds-avatar.ts +36 -3
  282. package/src/web-components/ui/bds-notification-banner.spec.ts +45 -0
  283. package/src/web-components/ui/bds-notification-banner.ts +53 -1
  284. package/src/web-components/ui/bds-section-header.spec.ts +36 -3
  285. package/src/web-components/ui/bds-section-header.ts +32 -10
  286. package/src/web-components/ui/bds-separator.spec.ts +8 -0
  287. package/src/web-components/ui/bds-separator.ts +7 -0
  288. package/src/web-components/ui/bds-table.spec.ts +11 -0
  289. package/src/web-components/ui/bds-table.ts +5 -5
  290. package/dist/web-components/chunk-QZAIRJ75.js +0 -111
@@ -2,6 +2,7 @@ import {
2
2
  HTMLAttributes,
3
3
  KeyboardEvent,
4
4
  ReactElement,
5
+ Fragment,
5
6
  cloneElement,
6
7
  isValidElement,
7
8
  useEffect,
@@ -20,8 +21,29 @@ export interface DropdownMenuItem {
20
21
  disabled?: boolean;
21
22
  icon?: ReactElement;
22
23
  separator?: boolean;
24
+ /** Renders the item in the error colour, for actions that delete or discard. */
25
+ destructive?: boolean;
26
+ /** Keyboard shortcut as display text. Decorative (`aria-hidden`); the consumer wires the actual key. */
27
+ shortcut?: string;
28
+ /** Consecutive items with the same group render inside a labelled `role="group"`. */
29
+ group?: string;
23
30
  }
24
31
 
32
+ interface IndexedItem {
33
+ item: DropdownMenuItem;
34
+ index: number;
35
+ }
36
+
37
+ /** Splits items into runs of consecutive items sharing the same `group`. */
38
+ const toSegments = (items: DropdownMenuItem[]): IndexedItem[][] =>
39
+ items.reduce<IndexedItem[][]>((segments, item, index) => {
40
+ const last = segments[segments.length - 1];
41
+ const startsNewSegment = !last || item.group === undefined || last[0].item.group !== item.group;
42
+ return startsNewSegment
43
+ ? [...segments, [{ item, index }]]
44
+ : [...segments.slice(0, -1), [...last, { item, index }]];
45
+ }, []);
46
+
25
47
  export interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
26
48
  trigger: ReactElement;
27
49
  items: DropdownMenuItem[];
@@ -125,23 +147,53 @@ export function DropdownMenu({
125
147
  role="menu"
126
148
  className={cn(css.menu, css[`--placement_${placement}`])}
127
149
  >
128
- {items.map((item, index) => (
129
- <li key={item.id} role="presentation">
130
- {item.separator && <hr className={css.separator} role="separator" />}
131
- <button
132
- ref={el => { itemRefs.current[index] = el; }}
133
- type="button"
134
- role="menuitem"
135
- disabled={item.disabled}
136
- className={css.item}
137
- onClick={() => { item.onClick?.(); close(true); }}
138
- onKeyDown={e => handleItemKeyDown(e, index)}
139
- >
140
- {item.icon && <span className={css.icon} aria-hidden="true">{item.icon}</span>}
141
- {item.label}
142
- </button>
143
- </li>
144
- ))}
150
+ {toSegments(items).map(segment => {
151
+ const { item: first } = segment[0];
152
+ const groupLabel = first.group;
153
+ const renderSeparator = () => (
154
+ <li role="presentation" className={css.separatorRow}>
155
+ <hr className={css.separator} role="separator" />
156
+ </li>
157
+ );
158
+ /* The first item's separator sits before the group, so the group holds only its label and items. */
159
+ const leadingSeparator = groupLabel !== undefined && first.separator;
160
+ const rows = segment.map(({ item, index }, position) => (
161
+ <Fragment key={item.id}>
162
+ {item.separator && !(leadingSeparator && position === 0) && renderSeparator()}
163
+ <li role="presentation">
164
+ <button
165
+ ref={el => { itemRefs.current[index] = el; }}
166
+ type="button"
167
+ role="menuitem"
168
+ disabled={item.disabled}
169
+ className={cn(css.item, item.destructive && css['--destructive'])}
170
+ onClick={() => { item.onClick?.(); close(true); }}
171
+ onKeyDown={e => handleItemKeyDown(e, index)}
172
+ >
173
+ {item.icon && <span className={css.icon} aria-hidden="true">{item.icon}</span>}
174
+ <span className={css.label}>{item.label}</span>
175
+ {item.shortcut && <span className={css.shortcut} aria-hidden="true">{item.shortcut}</span>}
176
+ </button>
177
+ </li>
178
+ </Fragment>
179
+ ));
180
+
181
+ return groupLabel === undefined ? (
182
+ rows
183
+ ) : (
184
+ <Fragment key={`group-${first.id}`}>
185
+ {leadingSeparator && renderSeparator()}
186
+ <li role="presentation">
187
+ <ul role="group" aria-labelledby={`${menuId}-group-${first.id}`} className={css.group}>
188
+ <li role="presentation" id={`${menuId}-group-${first.id}`} className={css.groupLabel}>
189
+ {groupLabel}
190
+ </li>
191
+ {rows}
192
+ </ul>
193
+ </li>
194
+ </Fragment>
195
+ );
196
+ })}
145
197
  </ul>
146
198
  )}
147
199
  </div>
@@ -65,5 +65,6 @@
65
65
 
66
66
  .checkboxError {
67
67
  --bdc_color: var(--bdc_color--error);
68
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
68
69
  }
69
70
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { Checkbox } from './Checkbox';
@@ -42,4 +44,9 @@ describe('Checkbox', () => {
42
44
  const checkbox = screen.getByLabelText('Accept terms');
43
45
  expect(checkbox.id).not.toBe('consumer-supplied-id');
44
46
  });
47
+
48
+ it('widens the outline in the error state', () => {
49
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Checkbox/Checkbox.module.css'), 'utf8');
50
+ expect(css).toMatch(/\.checkboxError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
51
+ });
45
52
  });
@@ -57,3 +57,4 @@ All public override tokens. Set on the CheckboxGroup or any ancestor; they casca
57
57
  - `disabled` on the group sets the HTML `disabled` attribute on the `<fieldset>`, which disables all child inputs
58
58
  - When `required` is true, the legend shows a visible `*` (hidden from assistive tech via `aria-hidden`) plus a visually-hidden "(required)" span, so the fieldset's accessible name resolves to `"<legend> (required)"` (e.g. `"Contact preferences (required)"`). `aria-required` is intentionally **not** set on the `<fieldset>` — it is not supported ARIA on a `role=group` element and would be ignored by assistive tech
59
59
  - Error and hint messages are linked via `aria-describedby`
60
+ - With `error`, the child controls show the error ring (`--bdc-outline_width--error`, `--bdc_color--error`) and the message gets a warning icon. Message hooks (`--formMessage_*`) are listed on FormInput
@@ -42,4 +42,11 @@
42
42
  flex-direction: column;
43
43
  gap: var(--checkboxGroup_items-gap, var(--bds-space_xs));
44
44
  }
45
+
46
+ /* Error ring on the controls inside; state rules on the control itself
47
+ (checked, focus) still win because they have higher specificity. */
48
+ .groupError input {
49
+ --bdc_color: var(--bdc_color--error);
50
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
51
+ }
45
52
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import { Checkbox } from '../Checkbox';
3
5
  import { CheckboxGroup } from './CheckboxGroup';
@@ -114,4 +116,24 @@ describe('CheckboxGroup', () => {
114
116
  );
115
117
  expect(screen.getByRole('group')).toBeDisabled();
116
118
  });
119
+
120
+ it('marks the group with the error class so the controls inside show the error ring', () => {
121
+ render(
122
+ <CheckboxGroup legend="Pick" error="Required">
123
+ <Checkbox label="One" name="one" value="1" />
124
+ </CheckboxGroup>
125
+ );
126
+ expect(screen.getByRole('group', { name: 'Pick' }).className).toContain('groupError');
127
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css'), 'utf8');
128
+ expect(css).toMatch(/\.groupError input \{[^}]*--bdc_color: var\(--bdc_color--error\)[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
129
+ });
130
+
131
+ it('does not mark the group without an error', () => {
132
+ render(
133
+ <CheckboxGroup legend="Pick">
134
+ <Checkbox label="One" name="one" value="1" />
135
+ </CheckboxGroup>
136
+ );
137
+ expect(screen.getByRole('group', { name: 'Pick' }).className).not.toContain('groupError');
138
+ });
117
139
  });
@@ -28,7 +28,7 @@ export function CheckboxGroup({
28
28
  return (
29
29
  <fieldset
30
30
  {...rest}
31
- className={cn(css.group, className)}
31
+ className={cn(css.group, error && css.groupError, className)}
32
32
  aria-describedby={describedBy}
33
33
  >
34
34
  <legend className={css.legend}>
@@ -45,6 +45,7 @@
45
45
 
46
46
  .inputError {
47
47
  --bdc_color: var(--bdc_color--error);
48
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
48
49
  }
49
50
 
50
51
  .chevron {
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { Combobox } from './Combobox';
@@ -125,4 +127,9 @@ describe('Combobox', () => {
125
127
  expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
126
128
  expect(combobox).not.toHaveAttribute('aria-activedescendant');
127
129
  });
130
+
131
+ it('widens the outline in the error state', () => {
132
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Combobox/Combobox.module.css'), 'utf8');
133
+ expect(css).toMatch(/\.inputError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
134
+ });
128
135
  });
@@ -43,9 +43,18 @@ Text input field with label, optional hint, and error state. The label and input
43
43
  <tr><td>`--formInput_border`</td><td>`none`</td><td>Border</td></tr>
44
44
  <tr><td>`--formInput_radius`</td><td>`var(--bds-border_radius--full)`</td><td>Corner radius</td></tr>
45
45
  <tr><td>`--formInput_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Box shadow</td></tr>
46
+ <tr><td>`--formMessage_font-size`</td><td>`var(--bds-font_size--body--s)`</td><td>Font size of the error and hint message (shared by all form controls)</td></tr>
47
+ <tr><td>`--formMessage_gap`</td><td>`var(--bds-space_xxs)`</td><td>Gap between the error icon and the error text</td></tr>
48
+ <tr><td>`--formMessage_icon-size`</td><td>`1.15em`</td><td>Size of the error icon</td></tr>
49
+ <tr><td>`--formMessage_color--error`</td><td>`var(--bds-color_error_on-bg)`</td><td>Error message colour</td></tr>
50
+ <tr><td>`--formMessage_font-weight--error`</td><td>`var(--bds-font_weight--semibold)`</td><td>Error message weight</td></tr>
51
+ <tr><td>`--formMessage_color--hint`</td><td>`inherit`</td><td>Hint colour</td></tr>
52
+ <tr><td>`--formMessage_font-style--hint`</td><td>`italic`</td><td>Hint font style</td></tr>
46
53
  </tbody>
47
54
  </table>
48
55
 
56
+ In the error state the control draws a `--bdc-outline_width--error` (2px) ring in `--bdc_color--error`, and the message gets a warning icon.
57
+
49
58
  `--input_*` (e.g. `--input_border`, `--input_radius`, `--input_shadow`, `--input_color`, `--input_color_bg`) are deprecated aliases for the `--formInput_*` tokens above — they still work but `--formInput_*` is the documented, forward-compatible name.
50
59
 
51
60
 
@@ -42,5 +42,6 @@
42
42
 
43
43
  .inputError {
44
44
  --bdc_color: var(--bdc_color--error);
45
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
45
46
  }
46
47
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { FormInput } from './FormInput';
@@ -60,4 +62,9 @@ describe('FormInput', () => {
60
62
  render(<FormInput label="Email" name="email" ariaLabel="Deprecated label" aria-label="Native label" />);
61
63
  expect(screen.getByRole('textbox')).toHaveAccessibleName('Deprecated label');
62
64
  });
65
+
66
+ it('widens the outline in the error state', () => {
67
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/FormInput/FormInput.module.css'), 'utf8');
68
+ expect(css).toMatch(/\.inputError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
69
+ });
63
70
  });
@@ -49,6 +49,7 @@
49
49
 
50
50
  .inputError {
51
51
  --bdc_color: var(--bdc_color--error);
52
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
52
53
  }
53
54
 
54
55
  .stepper {
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { useState } from 'react';
2
4
  import { render, screen } from '@testing-library/react';
3
5
  import userEvent from '@testing-library/user-event';
@@ -97,4 +99,9 @@ describe('NumberInput', () => {
97
99
  expect(input).toHaveValue(null);
98
100
  expect(onChange).not.toHaveBeenCalledWith(0);
99
101
  });
102
+
103
+ it('widens the outline in the error state', () => {
104
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/NumberInput/NumberInput.module.css'), 'utf8');
105
+ expect(css).toMatch(/\.inputError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
106
+ });
100
107
  });
@@ -78,5 +78,6 @@
78
78
 
79
79
  .radioError {
80
80
  --bdc_color: var(--bdc_color--error);
81
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
81
82
  }
82
83
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { Radio } from './Radio';
@@ -50,4 +52,9 @@ describe('Radio', () => {
50
52
  const radio = screen.getByLabelText('Option');
51
53
  expect(radio).toHaveAttribute('id', 'custom-radio-id');
52
54
  });
55
+
56
+ it('widens the outline in the error state', () => {
57
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Radio/Radio.module.css'), 'utf8');
58
+ expect(css).toMatch(/\.radioError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
59
+ });
53
60
  });
@@ -57,4 +57,5 @@ All public override tokens. Set on the RadioGroup or any ancestor; they cascade.
57
57
  - `disabled` on the group sets the HTML `disabled` attribute on the `<fieldset>`, which disables all child inputs
58
58
  - When `required` is true, the legend shows a visible `*` (hidden from assistive tech via `aria-hidden`) plus a visually-hidden "(required)" span, so the fieldset's accessible name resolves to `"<legend> (required)"` (e.g. `"Preferred contact (required)"`). `aria-required` is intentionally **not** set on the `<fieldset>` — it is not supported ARIA on a `role=group` element and would be ignored by assistive tech
59
59
  - Error and hint messages are linked via `aria-describedby`
60
+ - With `error`, the child controls show the error ring (`--bdc-outline_width--error`, `--bdc_color--error`) and the message gets a warning icon. Message hooks (`--formMessage_*`) are listed on FormInput
60
61
  - Arrow keys move between options within the group; `Tab` moves focus out of the group
@@ -42,4 +42,11 @@
42
42
  flex-direction: column;
43
43
  gap: var(--radioGroup_items-gap, var(--bds-space_xs));
44
44
  }
45
+
46
+ /* Error ring on the controls inside; state rules on the control itself
47
+ (checked, focus) still win because they have higher specificity. */
48
+ .groupError input {
49
+ --bdc_color: var(--bdc_color--error);
50
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
51
+ }
45
52
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import { Radio } from '../Radio';
3
5
  import { RadioGroup } from './RadioGroup';
@@ -102,4 +104,24 @@ describe('RadioGroup', () => {
102
104
  );
103
105
  expect(screen.getByRole('group')).toBeDisabled();
104
106
  });
107
+
108
+ it('marks the group with the error class so the controls inside show the error ring', () => {
109
+ render(
110
+ <RadioGroup legend="Pick" error="Required">
111
+ <Radio label="One" name="one" value="1" />
112
+ </RadioGroup>
113
+ );
114
+ expect(screen.getByRole('group', { name: 'Pick' }).className).toContain('groupError');
115
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/RadioGroup/RadioGroup.module.css'), 'utf8');
116
+ expect(css).toMatch(/\.groupError input \{[^}]*--bdc_color: var\(--bdc_color--error\)[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
117
+ });
118
+
119
+ it('does not mark the group without an error', () => {
120
+ render(
121
+ <RadioGroup legend="Pick">
122
+ <Radio label="One" name="one" value="1" />
123
+ </RadioGroup>
124
+ );
125
+ expect(screen.getByRole('group', { name: 'Pick' }).className).not.toContain('groupError');
126
+ });
105
127
  });
@@ -28,7 +28,7 @@ export function RadioGroup({
28
28
  return (
29
29
  <fieldset
30
30
  {...rest}
31
- className={cn(css.group, className)}
31
+ className={cn(css.group, error && css.groupError, className)}
32
32
  aria-describedby={describedBy}
33
33
  >
34
34
  <legend className={css.legend}>
@@ -46,6 +46,7 @@
46
46
 
47
47
  .selectError {
48
48
  --bdc_color: var(--bdc_color--error);
49
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
49
50
  }
50
51
 
51
52
  .chevron {
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { Select } from './Select';
@@ -93,4 +95,9 @@ describe('Select', () => {
93
95
  expect(select).toHaveAttribute('aria-describedby', 'custom-iderror');
94
96
  expect(screen.getByText('Required')).toHaveAttribute('id', 'custom-iderror');
95
97
  });
98
+
99
+ it('widens the outline in the error state', () => {
100
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Select/Select.module.css'), 'utf8');
101
+ expect(css).toMatch(/\.selectError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
102
+ });
96
103
  });
@@ -74,6 +74,7 @@
74
74
  /* Error state — declared before checked/focus so higher-specificity states can override */
75
75
  .switch.switchError + .track {
76
76
  --bdc_color: var(--switch_border-color--error, var(--bds-color_error));
77
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
77
78
  }
78
79
 
79
80
  /* Checked state via CSS sibling combinator */
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { Switch } from './Switch';
@@ -88,4 +90,9 @@ describe('Switch', () => {
88
90
  render(<Switch label="Toggle" name="toggle" role="checkbox" />);
89
91
  expect(screen.getByRole('switch')).toBeInTheDocument();
90
92
  });
93
+
94
+ it('widens the outline in the error state', () => {
95
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Switch/Switch.module.css'), 'utf8');
96
+ expect(css).toMatch(/\.switchError \+ \.track \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
97
+ });
91
98
  });
@@ -40,5 +40,6 @@
40
40
 
41
41
  .textareaError {
42
42
  --bdc_color: var(--bdc_color--error);
43
+ --bdc-outline_width: var(--bdc-outline_width--error, 2px);
43
44
  }
44
45
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { Textarea } from './Textarea';
@@ -63,4 +65,9 @@ describe('Textarea', () => {
63
65
  ?.split(' ')
64
66
  .forEach((describedById) => expect(describedById.startsWith('custom-id')).toBe(true));
65
67
  });
68
+
69
+ it('widens the outline in the error state', () => {
70
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Textarea/Textarea.module.css'), 'utf8');
71
+ expect(css).toMatch(/\.textareaError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
72
+ });
66
73
  });
@@ -1,15 +1,26 @@
1
1
  @layer boostdev.component {
2
2
  .error,
3
3
  .hint {
4
- font-size: var(--bds-font_size--body--s);
4
+ font-size: var(--formMessage_font-size, var(--bds-font_size--body--s));
5
5
  margin-inline-start: var(--labelPosition, 0);
6
6
  }
7
7
 
8
8
  .error {
9
- color: var(--bds-color_error_on-bg);
9
+ display: flex;
10
+ align-items: flex-start;
11
+ gap: var(--formMessage_gap, var(--bds-space_xxs));
12
+ color: var(--formMessage_color--error, var(--bds-color_error_on-bg));
13
+ font-weight: var(--formMessage_font-weight--error, var(--bds-font_weight--semibold));
10
14
  }
11
15
 
12
16
  .hint {
13
- font-style: italic;
17
+ color: var(--formMessage_color--hint, inherit);
18
+ font-style: var(--formMessage_font-style--hint, italic);
19
+ }
20
+
21
+ .icon {
22
+ inline-size: var(--formMessage_icon-size, 1.15em);
23
+ block-size: var(--formMessage_icon-size, 1.15em);
24
+ flex-shrink: 0;
14
25
  }
15
26
  }
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import { Message } from './Message';
3
5
 
@@ -26,4 +28,24 @@ describe('Message', () => {
26
28
  render(<Message inputId="field-1" type="error" message="Required" />);
27
29
  expect(screen.getByRole('alert')).toHaveAttribute('id', 'field-1error');
28
30
  });
31
+
32
+ it('renders an aria-hidden icon before the error text', () => {
33
+ render(<Message inputId="field-1" type="error" message="Required" />);
34
+ const icon = screen.getByRole('alert').querySelector('svg');
35
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
36
+ expect(screen.getByRole('alert')).toHaveTextContent('Required');
37
+ });
38
+
39
+ it('renders no icon for a hint', () => {
40
+ render(<Message inputId="field-1" type="hint" message="Hint" />);
41
+ expect(screen.getByText('Hint').querySelector('svg')).toBeNull();
42
+ });
43
+
44
+ it('exposes the message hooks in the stylesheet', () => {
45
+ const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/atoms/Message.module.css'), 'utf8');
46
+ ['--formMessage_gap', '--formMessage_icon-size', '--formMessage_color--error', '--formMessage_font-weight--error',
47
+ '--formMessage_color--hint', '--formMessage_font-style--hint', '--formMessage_font-size'].forEach((hook) => {
48
+ expect(css).toContain(hook);
49
+ });
50
+ });
29
51
  });
@@ -13,6 +13,11 @@ export const Message = ({ message, type, inputId, className }: MessageProps) =>
13
13
 
14
14
  return (
15
15
  <p id={inputId + type} role={type === 'error' ? 'alert' : undefined} className={cn(css[type], className)}>
16
+ {type === 'error' && (
17
+ <svg className={css.icon} aria-hidden="true" viewBox="0 0 256 256" fill="currentColor">
18
+ <path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z" />
19
+ </svg>
20
+ )}
16
21
  {message}
17
22
  </p>
18
23
  );
@@ -31,6 +31,17 @@ Heading block with optional subtitle for introducing a content section.
31
31
  ### Centered
32
32
  <Canvas of={Stories.Centered} />
33
33
 
34
+ ### Gradient
35
+ Set `appearance="gradient"` for a gradient-filled title. The gradient spans the text, not the row. Where `background-clip: text` is unsupported the title keeps its normal colour; in forced-colors mode it uses `CanvasText`.
36
+
37
+ <Canvas of={Stories.Gradient} />
38
+
39
+ <Canvas of={Stories.GradientExtraSmall} />
40
+
41
+ <Canvas of={Stories.GradientSmall} />
42
+
43
+ <Canvas of={Stories.GradientLarge} />
44
+
34
45
  ## Props
35
46
 
36
47
  <ArgTypes of={Stories} />
@@ -43,12 +54,14 @@ Heading block with optional subtitle for introducing a content section.
43
54
  <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
44
55
  </thead>
45
56
  <tbody>
46
- <tr><td>`--sectionHeader_title-color`</td><td>`var(--bds-color_on-bg)`</td><td>Title text colour</td></tr>
47
- <tr><td>`--sectionHeader_title-size`</td><td>varies by `size` prop</td><td>Title font-size (overrides the variant default)</td></tr>
57
+ <tr><td>`--sectionHeader_title-color`</td><td>`var(--bds-color_on-bg)`</td><td>Title text colour; no effect with `appearance="gradient"`, where the colour comes from `--sectionHeader_title-gradient`</td></tr>
58
+ <tr><td>`--sectionHeader_title-font-family`</td><td>`var(--bds-font_family--heading)`</td><td>Title font family</td></tr>
59
+ <tr><td>`--sectionHeader_title-gradient`</td><td>`linear-gradient(90deg, var(--bds-color_primary--strong), var(--bds-color_accent-3--strong))`</td><td>Title gradient, only with `appearance="gradient"`</td></tr>
60
+ <tr><td>`--sectionHeader_title-size`</td><td>`xs` body, `small` heading-3, `medium` heading-2, `large` heading-1</td><td>Title font-size (overrides the variant default)</td></tr>
48
61
  <tr><td>`--sectionHeader_title-line-height`</td><td>varies by `size` prop</td><td>Title line-height</td></tr>
49
- <tr><td>`--sectionHeader_title-weight`</td><td>`var(--bds-font_weight--bold)`</td><td>Title font-weight</td></tr>
50
- <tr><td>`--sectionHeader_subtitle-color`</td><td>`var(--bds-color_grey--strong)`</td><td>Subtitle text colour</td></tr>
51
- <tr><td>`--sectionHeader_subtitle-size`</td><td>varies by `size` prop</td><td>Subtitle font-size</td></tr>
62
+ <tr><td>`--sectionHeader_title-weight`</td><td>`var(--bds-font_weight--heading)`</td><td>Title font-weight</td></tr>
63
+ <tr><td>`--sectionHeader_subtitle-color`</td><td>`var(--bds-color_on-bg--muted)`</td><td>Subtitle text colour</td></tr>
64
+ <tr><td>`--sectionHeader_subtitle-size`</td><td>`xs` body--s, `small`/`medium` body, `large` heading-3</td><td>Subtitle font-size</td></tr>
52
65
  <tr><td>`--sectionHeader_subtitle-line-height`</td><td>`1.4` (`xs`/`small`), `1.3` (`medium`/`large`)</td><td>Subtitle line-height</td></tr>
53
66
  <tr><td>`--sectionHeader_gap`</td><td>`var(--bds-space_m)`</td><td>Gap between title and subtitle</td></tr>
54
67
  </tbody>