@boostdev/design-system-components 2.7.1 → 2.11.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 (535) hide show
  1. package/AGENTS.md +54 -46
  2. package/README.md +34 -20
  3. package/dist/client.cjs +1317 -779
  4. package/dist/client.css +1029 -812
  5. package/dist/client.d.cts +88 -26
  6. package/dist/client.d.ts +88 -26
  7. package/dist/client.js +1278 -732
  8. package/dist/index.cjs +1317 -779
  9. package/dist/index.css +1029 -812
  10. package/dist/index.d.cts +88 -26
  11. package/dist/index.d.ts +88 -26
  12. package/dist/index.js +1278 -732
  13. package/dist/web-components/chunk-2OLVQUBU.js +193 -0
  14. package/dist/web-components/chunk-3AY2YKCM.js +223 -0
  15. package/dist/web-components/chunk-3WAV6MDN.js +212 -0
  16. package/dist/web-components/{chunk-CZBIVR4V.js → chunk-47AT5KZD.js} +88 -16
  17. package/dist/web-components/chunk-4A2YYTYN.js +93 -0
  18. package/dist/web-components/{chunk-Q4T5BJT6.js → chunk-62NK3ZSC.js} +18 -3
  19. package/dist/web-components/chunk-66M6ETWD.js +77 -0
  20. package/dist/web-components/{chunk-NCPQ7AF3.js → chunk-6G22I2CK.js} +18 -3
  21. package/dist/web-components/{chunk-QDST57JC.js → chunk-7ZOJ7N4E.js} +14 -4
  22. package/dist/web-components/{chunk-5R6IFELV.js → chunk-AIBOHNDU.js} +11 -1
  23. package/dist/web-components/{chunk-ITHLKR7Q.js → chunk-AP4RDUL2.js} +16 -5
  24. package/dist/web-components/{chunk-QWPACK5L.js → chunk-BPYKZVDK.js} +11 -1
  25. package/dist/web-components/{chunk-YLVTEY23.js → chunk-BTCMJQKB.js} +53 -21
  26. package/dist/web-components/chunk-CUKQ2L5B.js +245 -0
  27. package/dist/web-components/chunk-DIZDCL7Z.js +14 -0
  28. package/dist/web-components/{chunk-PC3IOQAH.js → chunk-DV5T43ZC.js} +27 -7
  29. package/dist/web-components/{chunk-2QA62WCQ.js → chunk-EF7IVYTY.js} +10 -0
  30. package/dist/web-components/{chunk-L3XHOMN6.js → chunk-F3ZDNHZZ.js} +26 -1
  31. package/dist/web-components/chunk-FEDABOR5.js +248 -0
  32. package/dist/web-components/{chunk-6I2DBFQ7.js → chunk-HGFCJXOQ.js} +36 -5
  33. package/dist/web-components/{chunk-SOQQ7RVJ.js → chunk-IPGHUCJT.js} +23 -8
  34. package/dist/web-components/chunk-ITD2MV5K.js +392 -0
  35. package/dist/web-components/{chunk-CM3KYZ6Z.js → chunk-JH6LQKLK.js} +78 -6
  36. package/dist/web-components/chunk-JMCXAAFK.js +120 -0
  37. package/dist/web-components/{chunk-KUI55GFB.js → chunk-JUPCZVVW.js} +50 -18
  38. package/dist/web-components/chunk-JV5QT2OU.js +146 -0
  39. package/dist/web-components/{chunk-3GFWLSLS.js → chunk-KA3VPKMY.js} +29 -6
  40. package/dist/web-components/{chunk-CR5PGUHV.js → chunk-KJGRFDFI.js} +26 -11
  41. package/dist/web-components/{chunk-PY62FTRW.js → chunk-KZPRFY5P.js} +68 -33
  42. package/dist/web-components/{chunk-X46KL5BK.js → chunk-LDIPCTRN.js} +18 -1
  43. package/dist/web-components/chunk-M3ECULRH.js +174 -0
  44. package/dist/web-components/chunk-NBN2ENWJ.js +90 -0
  45. package/dist/web-components/chunk-NIN3JO6Q.js +189 -0
  46. package/dist/web-components/{chunk-2FGATTGT.js → chunk-NTQ4TCUT.js} +76 -55
  47. package/dist/web-components/chunk-OY74D4X6.js +302 -0
  48. package/dist/web-components/{chunk-3S42DZ7W.js → chunk-P24JYC5V.js} +7 -0
  49. package/dist/web-components/{chunk-BKIHQYJR.js → chunk-PXPPKCLT.js} +17 -2
  50. package/dist/web-components/{chunk-TQ7IQ4GZ.js → chunk-QEF2IRID.js} +71 -29
  51. package/dist/web-components/chunk-QZAIRJ75.js +111 -0
  52. package/dist/web-components/{chunk-6I3CFC26.js → chunk-RSZ3BWBK.js} +20 -5
  53. package/dist/web-components/chunk-RVF7W653.js +101 -0
  54. package/dist/web-components/chunk-SEO2F5KJ.js +234 -0
  55. package/dist/web-components/{chunk-VMMVWLXL.js → chunk-SFZ6Q4BP.js} +10 -0
  56. package/dist/web-components/{chunk-LY62SAVN.js → chunk-SYNLFKD3.js} +12 -2
  57. package/dist/web-components/chunk-TMWXRHGF.js +192 -0
  58. package/dist/web-components/{chunk-65JXQOBA.js → chunk-U3YZE6R6.js} +51 -7
  59. package/dist/web-components/chunk-UO7ROSHD.js +424 -0
  60. package/dist/web-components/chunk-VANAKB6D.js +152 -0
  61. package/dist/web-components/{chunk-PXZWYO6Z.js → chunk-VED2DFJ6.js} +108 -29
  62. package/dist/web-components/chunk-W6H5ID2U.js +161 -0
  63. package/dist/web-components/{chunk-57TRNCWQ.js → chunk-WFVKU4GX.js} +22 -11
  64. package/dist/web-components/{chunk-OQTRTMAB.js → chunk-WIBAN7KP.js} +10 -0
  65. package/dist/web-components/chunk-XI7KRAH7.js +54 -0
  66. package/dist/web-components/{chunk-F5E3IT7G.js → chunk-XIZWAWMJ.js} +50 -11
  67. package/dist/web-components/{chunk-FXURM6D5.js → chunk-YFAXGRPR.js} +10 -0
  68. package/dist/web-components/globals.js +1 -1
  69. package/dist/web-components/index.d.ts +77 -707
  70. package/dist/web-components/index.js +339 -2066
  71. package/dist/web-components/interaction/bds-accordion-item.js +1 -1
  72. package/dist/web-components/interaction/bds-accordion.js +1 -1
  73. package/dist/web-components/interaction/bds-button.d.ts +10 -2
  74. package/dist/web-components/interaction/bds-button.js +1 -1
  75. package/dist/web-components/interaction/bds-collapsible.d.ts +7 -2
  76. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  77. package/dist/web-components/interaction/bds-dialog.d.ts +11 -3
  78. package/dist/web-components/interaction/bds-dialog.js +1 -1
  79. package/dist/web-components/interaction/bds-drawer.d.ts +7 -1
  80. package/dist/web-components/interaction/bds-drawer.js +1 -1
  81. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +51 -0
  82. package/dist/web-components/interaction/bds-dropdown-menu-item.js +7 -0
  83. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +70 -0
  84. package/dist/web-components/interaction/bds-dropdown-menu.js +7 -0
  85. package/dist/web-components/interaction/bds-popover.d.ts +14 -3
  86. package/dist/web-components/interaction/bds-popover.js +1 -1
  87. package/dist/web-components/interaction/bds-skip-link.js +1 -1
  88. package/dist/web-components/interaction/bds-tab-panel.js +1 -1
  89. package/dist/web-components/interaction/bds-tab.js +1 -1
  90. package/dist/web-components/interaction/bds-tabs.d.ts +1 -0
  91. package/dist/web-components/interaction/bds-tabs.js +1 -1
  92. package/dist/web-components/interaction/bds-toast-provider.js +1 -1
  93. package/dist/web-components/interaction/bds-tooltip.d.ts +19 -3
  94. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  95. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +83 -0
  96. package/dist/web-components/interaction/form/bds-checkbox-group.js +7 -0
  97. package/dist/web-components/interaction/form/bds-checkbox.d.ts +6 -0
  98. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  99. package/dist/web-components/interaction/form/bds-combobox.d.ts +18 -0
  100. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  101. package/dist/web-components/interaction/form/bds-field-group.d.ts +89 -0
  102. package/dist/web-components/interaction/form/bds-field-group.js +7 -0
  103. package/dist/web-components/interaction/form/bds-file-input.d.ts +1 -1
  104. package/dist/web-components/interaction/form/bds-file-input.js +1 -1
  105. package/dist/web-components/interaction/form/bds-form-input.d.ts +92 -0
  106. package/dist/web-components/interaction/form/bds-form-input.js +7 -0
  107. package/dist/web-components/interaction/form/bds-number-input.d.ts +18 -1
  108. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  109. package/dist/web-components/interaction/form/bds-radio-group.d.ts +81 -0
  110. package/dist/web-components/interaction/form/bds-radio-group.js +7 -0
  111. package/dist/web-components/interaction/form/bds-radio.d.ts +34 -6
  112. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  113. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +4 -4
  114. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  115. package/dist/web-components/interaction/form/bds-select.d.ts +22 -1
  116. package/dist/web-components/interaction/form/bds-select.js +1 -1
  117. package/dist/web-components/interaction/form/bds-slider.d.ts +42 -10
  118. package/dist/web-components/interaction/form/bds-slider.js +1 -1
  119. package/dist/web-components/interaction/form/bds-switch.d.ts +20 -0
  120. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  121. package/dist/web-components/interaction/form/bds-textarea.d.ts +23 -3
  122. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  123. package/dist/web-components/layout/bds-grid-item.d.ts +63 -0
  124. package/dist/web-components/layout/bds-grid-item.js +7 -0
  125. package/dist/web-components/layout/bds-grid.d.ts +108 -0
  126. package/dist/web-components/layout/bds-grid.js +7 -0
  127. package/dist/web-components/ui/bds-alert.d.ts +16 -2
  128. package/dist/web-components/ui/bds-alert.js +1 -1
  129. package/dist/web-components/ui/bds-avatar.d.ts +2 -1
  130. package/dist/web-components/ui/bds-avatar.js +1 -1
  131. package/dist/web-components/ui/bds-badge.js +1 -1
  132. package/dist/web-components/ui/bds-breadcrumb.d.ts +3 -1
  133. package/dist/web-components/ui/bds-breadcrumb.js +1 -1
  134. package/dist/web-components/ui/bds-button-container.d.ts +52 -0
  135. package/dist/web-components/ui/bds-button-container.js +7 -0
  136. package/dist/web-components/ui/bds-button-group.d.ts +21 -0
  137. package/dist/web-components/ui/bds-button-group.js +8 -0
  138. package/dist/web-components/ui/bds-calendar.d.ts +121 -0
  139. package/dist/web-components/ui/bds-calendar.js +7 -0
  140. package/dist/web-components/ui/bds-card.d.ts +12 -3
  141. package/dist/web-components/ui/bds-card.js +1 -1
  142. package/dist/web-components/ui/bds-carousel.d.ts +98 -0
  143. package/dist/web-components/ui/bds-carousel.js +7 -0
  144. package/dist/web-components/ui/bds-description-list.js +1 -1
  145. package/dist/web-components/ui/bds-icon-wrapper.d.ts +0 -9
  146. package/dist/web-components/ui/bds-icon-wrapper.js +1 -1
  147. package/dist/web-components/ui/bds-link.js +1 -1
  148. package/dist/web-components/ui/bds-loading.js +1 -1
  149. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  150. package/dist/web-components/ui/bds-pagination.d.ts +47 -0
  151. package/dist/web-components/ui/bds-pagination.js +7 -0
  152. package/dist/web-components/ui/bds-progress-circle.d.ts +3 -1
  153. package/dist/web-components/ui/bds-progress-circle.js +1 -1
  154. package/dist/web-components/ui/bds-progress.d.ts +3 -1
  155. package/dist/web-components/ui/bds-progress.js +1 -1
  156. package/dist/web-components/ui/bds-rating.js +1 -1
  157. package/dist/web-components/ui/bds-section-header.d.ts +18 -4
  158. package/dist/web-components/ui/bds-section-header.js +1 -1
  159. package/dist/web-components/ui/bds-separator.js +1 -1
  160. package/dist/web-components/ui/bds-skeleton.js +1 -1
  161. package/dist/web-components/ui/bds-table.d.ts +52 -0
  162. package/dist/web-components/ui/bds-table.js +7 -0
  163. package/dist/web-components/ui/bds-typography.d.ts +1 -0
  164. package/dist/web-components/ui/bds-typography.js +1 -1
  165. package/package.json +60 -3
  166. package/src/components/interaction/Button/Button.module.css +23 -14
  167. package/src/components/interaction/Button/Button.spec.tsx +20 -0
  168. package/src/components/interaction/Button/Button.tsx +2 -2
  169. package/src/components/interaction/Command/Command.mdx +5 -2
  170. package/src/components/interaction/Command/Command.module.css +23 -7
  171. package/src/components/interaction/Command/Command.spec.tsx +35 -0
  172. package/src/components/interaction/Command/Command.tsx +15 -4
  173. package/src/components/interaction/Command/index.ts +1 -1
  174. package/src/components/interaction/Dialog/Dialog.mdx +4 -4
  175. package/src/components/interaction/Dialog/Dialog.module.css +20 -0
  176. package/src/components/interaction/Dialog/Dialog.spec.tsx +20 -0
  177. package/src/components/interaction/Dialog/Dialog.tsx +8 -3
  178. package/src/components/interaction/Dialog/index.ts +1 -0
  179. package/src/components/interaction/Drawer/Drawer.mdx +5 -2
  180. package/src/components/interaction/Drawer/Drawer.module.css +34 -3
  181. package/src/components/interaction/Drawer/Drawer.spec.tsx +67 -1
  182. package/src/components/interaction/Drawer/Drawer.tsx +30 -12
  183. package/src/components/interaction/Drawer/index.ts +1 -0
  184. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +2 -0
  185. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +34 -0
  186. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +15 -5
  187. package/src/components/interaction/DropdownMenu/index.ts +1 -0
  188. package/src/components/interaction/Popover/Popover.mdx +6 -2
  189. package/src/components/interaction/Popover/Popover.module.css +26 -18
  190. package/src/components/interaction/Popover/Popover.spec.tsx +38 -0
  191. package/src/components/interaction/Popover/Popover.tsx +19 -5
  192. package/src/components/interaction/Popover/index.ts +1 -0
  193. package/src/components/interaction/Rating/Rating.mdx +1 -1
  194. package/src/components/interaction/Rating/Rating.module.css +2 -2
  195. package/src/components/interaction/Rating/Rating.tsx +1 -1
  196. package/src/components/interaction/Rating/index.ts +1 -0
  197. package/src/components/interaction/Toast/Toast.mdx +3 -0
  198. package/src/components/interaction/Toast/Toast.module.css +35 -5
  199. package/src/components/interaction/Toast/Toast.spec.tsx +69 -1
  200. package/src/components/interaction/Toast/Toast.tsx +15 -9
  201. package/src/components/interaction/form/Checkbox/Checkbox.mdx +3 -0
  202. package/src/components/interaction/form/Checkbox/Checkbox.module.css +13 -6
  203. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +6 -0
  204. package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
  205. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +16 -1
  206. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +24 -8
  207. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +32 -2
  208. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +14 -2
  209. package/src/components/interaction/form/Combobox/Combobox.mdx +11 -4
  210. package/src/components/interaction/form/Combobox/Combobox.module.css +6 -6
  211. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +47 -0
  212. package/src/components/interaction/form/Combobox/Combobox.tsx +10 -5
  213. package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +9 -10
  214. package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +57 -23
  215. package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +129 -7
  216. package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +72 -12
  217. package/src/components/interaction/form/FileInput/FileInput.mdx +9 -1
  218. package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
  219. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +89 -1
  220. package/src/components/interaction/form/FileInput/FileInput.tsx +6 -2
  221. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -5
  222. package/src/components/interaction/form/FormInput/FormInput.module.css +17 -8
  223. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +20 -0
  224. package/src/components/interaction/form/FormInput/FormInput.tsx +7 -2
  225. package/src/components/interaction/form/NumberInput/NumberInput.mdx +5 -1
  226. package/src/components/interaction/form/NumberInput/NumberInput.module.css +6 -6
  227. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +51 -0
  228. package/src/components/interaction/form/NumberInput/NumberInput.tsx +36 -8
  229. package/src/components/interaction/form/Radio/Radio.mdx +8 -1
  230. package/src/components/interaction/form/Radio/Radio.module.css +20 -7
  231. package/src/components/interaction/form/Radio/Radio.spec.tsx +15 -0
  232. package/src/components/interaction/form/Radio/Radio.tsx +10 -4
  233. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +16 -1
  234. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +24 -8
  235. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +32 -2
  236. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +14 -2
  237. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +9 -3
  238. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +25 -12
  239. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +86 -5
  240. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +44 -4
  241. package/src/components/interaction/form/Select/Select.mdx +10 -4
  242. package/src/components/interaction/form/Select/Select.module.css +4 -4
  243. package/src/components/interaction/form/Select/Select.spec.tsx +34 -0
  244. package/src/components/interaction/form/Select/Select.tsx +21 -9
  245. package/src/components/interaction/form/Slider/Slider.mdx +4 -1
  246. package/src/components/interaction/form/Slider/Slider.module.css +32 -8
  247. package/src/components/interaction/form/Slider/Slider.spec.tsx +29 -0
  248. package/src/components/interaction/form/Slider/Slider.tsx +9 -7
  249. package/src/components/interaction/form/Switch/Switch.mdx +2 -2
  250. package/src/components/interaction/form/Switch/Switch.module.css +12 -13
  251. package/src/components/interaction/form/Switch/Switch.spec.tsx +20 -0
  252. package/src/components/interaction/form/Switch/Switch.tsx +2 -1
  253. package/src/components/interaction/form/Textarea/Textarea.mdx +5 -0
  254. package/src/components/interaction/form/Textarea/Textarea.module.css +5 -5
  255. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -0
  256. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -1
  257. package/src/components/interaction/form/atoms/Message.module.css +1 -1
  258. package/src/components/interaction/form/atoms/Message.spec.tsx +29 -0
  259. package/src/components/interaction/form/atoms/Message.tsx +1 -1
  260. package/src/components/layout/ButtonGroup/ButtonGroup.mdx +11 -0
  261. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -5
  262. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +14 -0
  263. package/src/components/layout/Card/Card.mdx +6 -1
  264. package/src/components/layout/Card/Card.module.css +26 -5
  265. package/src/components/layout/Card/Card.spec.tsx +7 -0
  266. package/src/components/layout/Card/Card.tsx +4 -3
  267. package/src/components/layout/Grid/Grid.spec.tsx +44 -0
  268. package/src/components/layout/Grid/Grid.tsx +5 -5
  269. package/src/components/layout/Grid/GridItem.tsx +6 -6
  270. package/src/components/layout/Grid/masonry.ts +31 -5
  271. package/src/components/layout/Grid/useIsomorphicLayoutEffect.ts +11 -0
  272. package/src/components/layout/IconWrapper/IconWrapper.mdx +8 -2
  273. package/src/components/layout/IconWrapper/IconWrapper.module.css +9 -6
  274. package/src/components/layout/SectionHeader/SectionHeader.mdx +4 -1
  275. package/src/components/layout/SectionHeader/SectionHeader.module.css +10 -9
  276. package/src/components/ui/Accordion/Accordion.mdx +4 -1
  277. package/src/components/ui/Accordion/Accordion.module.css +2 -1
  278. package/src/components/ui/Alert/Alert.mdx +21 -4
  279. package/src/components/ui/Alert/Alert.module.css +9 -4
  280. package/src/components/ui/Alert/Alert.spec.tsx +20 -0
  281. package/src/components/ui/Avatar/Avatar.mdx +8 -3
  282. package/src/components/ui/Avatar/Avatar.module.css +13 -8
  283. package/src/components/ui/Badge/Badge.mdx +3 -1
  284. package/src/components/ui/Breadcrumb/Breadcrumb.mdx +3 -0
  285. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +4 -0
  286. package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +12 -0
  287. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +11 -3
  288. package/src/components/ui/Calendar/Calendar.mdx +6 -0
  289. package/src/components/ui/Calendar/Calendar.module.css +68 -19
  290. package/src/components/ui/Calendar/Calendar.spec.tsx +139 -24
  291. package/src/components/ui/Calendar/Calendar.stories.tsx +14 -0
  292. package/src/components/ui/Calendar/Calendar.tsx +143 -154
  293. package/src/components/ui/Calendar/CalendarRange.mdx +39 -0
  294. package/src/components/ui/Calendar/CalendarRange.spec.tsx +119 -0
  295. package/src/components/ui/Calendar/CalendarRange.stories.tsx +51 -0
  296. package/src/components/ui/Calendar/CalendarRange.tsx +213 -0
  297. package/src/components/ui/Calendar/MonthGrid.tsx +141 -0
  298. package/src/components/ui/Calendar/calendar-core.spec.ts +176 -0
  299. package/src/components/ui/Calendar/calendar-core.ts +164 -0
  300. package/src/components/ui/Calendar/index.ts +1 -0
  301. package/src/components/ui/Carousel/Carousel.mdx +8 -3
  302. package/src/components/ui/Carousel/Carousel.module.css +7 -5
  303. package/src/components/ui/Carousel/Carousel.spec.tsx +67 -0
  304. package/src/components/ui/Carousel/Carousel.tsx +19 -7
  305. package/src/components/ui/Collapsible/Collapsible.mdx +17 -6
  306. package/src/components/ui/Collapsible/Collapsible.module.css +7 -2
  307. package/src/components/ui/Collapsible/Collapsible.spec.tsx +40 -0
  308. package/src/components/ui/Collapsible/Collapsible.tsx +19 -2
  309. package/src/components/ui/DescriptionList/DescriptionList.mdx +2 -0
  310. package/src/components/ui/Link/Link.mdx +4 -2
  311. package/src/components/ui/Loading/Loading.mdx +6 -3
  312. package/src/components/ui/Loading/Loading.module.css +7 -7
  313. package/src/components/ui/Loading/Loading.spec.tsx +12 -0
  314. package/src/components/ui/Loading/Loading.tsx +8 -3
  315. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +4 -0
  316. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +5 -0
  317. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +20 -0
  318. package/src/components/ui/Pagination/Pagination.mdx +29 -7
  319. package/src/components/ui/Pagination/Pagination.module.css +15 -14
  320. package/src/components/ui/Progress/Progress.mdx +6 -2
  321. package/src/components/ui/Progress/Progress.module.css +1 -1
  322. package/src/components/ui/Progress/Progress.spec.tsx +17 -0
  323. package/src/components/ui/Progress/Progress.tsx +2 -2
  324. package/src/components/ui/ProgressCircle/ProgressCircle.mdx +8 -4
  325. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +2 -2
  326. package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +11 -1
  327. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +1 -1
  328. package/src/components/ui/Separator/Separator.mdx +7 -0
  329. package/src/components/ui/Separator/Separator.module.css +1 -0
  330. package/src/components/ui/Skeleton/Skeleton.mdx +17 -0
  331. package/src/components/ui/Skeleton/Skeleton.module.css +12 -5
  332. package/src/components/ui/SkipLink/SkipLink.mdx +19 -0
  333. package/src/components/ui/SkipLink/SkipLink.module.css +8 -7
  334. package/src/components/ui/SkipLink/SkipLink.spec.tsx +28 -0
  335. package/src/components/ui/Table/Table.mdx +14 -0
  336. package/src/components/ui/Table/Table.module.css +13 -8
  337. package/src/components/ui/Table/Table.spec.tsx +56 -0
  338. package/src/components/ui/Table/Table.tsx +44 -6
  339. package/src/components/ui/Tabs/Tabs.mdx +16 -0
  340. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  341. package/src/components/ui/Tabs/Tabs.spec.tsx +20 -0
  342. package/src/components/ui/Tabs/Tabs.tsx +5 -1
  343. package/src/components/ui/Tooltip/Tooltip.mdx +9 -0
  344. package/src/components/ui/Tooltip/Tooltip.module.css +32 -12
  345. package/src/components/ui/Tooltip/Tooltip.spec.tsx +56 -0
  346. package/src/components/ui/Tooltip/Tooltip.tsx +26 -7
  347. package/src/components/ui/Typography/Typography.mdx +7 -0
  348. package/src/components/ui/Typography/Typography.module.css +5 -5
  349. package/src/index.ts +7 -3
  350. package/src/stories/DesignSystem/Borders.mdx +14 -9
  351. package/src/stories/DesignSystem/Colors.mdx +43 -41
  352. package/src/stories/DesignSystem/Overview.mdx +4 -3
  353. package/src/stories/Introduction.mdx +5 -5
  354. package/src/web-components/globals.ts +7 -0
  355. package/src/web-components/index.ts +2 -0
  356. package/src/web-components/interaction/BdsAccordion.mdx +10 -1
  357. package/src/web-components/interaction/BdsButton.mdx +15 -4
  358. package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
  359. package/src/web-components/interaction/BdsCollapsible.mdx +3 -3
  360. package/src/web-components/interaction/BdsDialog.mdx +12 -7
  361. package/src/web-components/interaction/BdsDialog.stories.tsx +2 -2
  362. package/src/web-components/interaction/BdsDrawer.mdx +9 -6
  363. package/src/web-components/interaction/BdsDropdownMenu.mdx +20 -1
  364. package/src/web-components/interaction/BdsPopover.mdx +7 -3
  365. package/src/web-components/interaction/BdsSkipLink.mdx +14 -1
  366. package/src/web-components/interaction/BdsTabs.mdx +18 -1
  367. package/src/web-components/interaction/BdsToastProvider.mdx +21 -3
  368. package/src/web-components/interaction/BdsTooltip.mdx +4 -4
  369. package/src/web-components/interaction/bds-accordion-item.ts +10 -0
  370. package/src/web-components/interaction/bds-accordion.spec.ts +41 -0
  371. package/src/web-components/interaction/bds-accordion.ts +28 -2
  372. package/src/web-components/interaction/bds-button.spec.ts +81 -0
  373. package/src/web-components/interaction/bds-button.ts +60 -13
  374. package/src/web-components/interaction/bds-collapsible.spec.ts +93 -2
  375. package/src/web-components/interaction/bds-collapsible.ts +49 -7
  376. package/src/web-components/interaction/bds-dialog.spec.ts +49 -0
  377. package/src/web-components/interaction/bds-dialog.ts +68 -11
  378. package/src/web-components/interaction/bds-drawer.spec.ts +93 -4
  379. package/src/web-components/interaction/bds-drawer.ts +54 -21
  380. package/src/web-components/interaction/bds-dropdown-menu-item.ts +12 -1
  381. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +63 -0
  382. package/src/web-components/interaction/bds-dropdown-menu.ts +45 -7
  383. package/src/web-components/interaction/bds-popover.spec.ts +80 -6
  384. package/src/web-components/interaction/bds-popover.ts +83 -32
  385. package/src/web-components/interaction/bds-skip-link.spec.ts +18 -0
  386. package/src/web-components/interaction/bds-skip-link.ts +29 -8
  387. package/src/web-components/interaction/bds-tab-panel.ts +10 -0
  388. package/src/web-components/interaction/bds-tab.spec.ts +25 -0
  389. package/src/web-components/interaction/bds-tab.ts +20 -5
  390. package/src/web-components/interaction/bds-tabs.spec.ts +92 -5
  391. package/src/web-components/interaction/bds-tabs.ts +42 -1
  392. package/src/web-components/interaction/bds-toast-provider.spec.ts +43 -0
  393. package/src/web-components/interaction/bds-toast-provider.ts +50 -21
  394. package/src/web-components/interaction/bds-tooltip.spec.ts +71 -0
  395. package/src/web-components/interaction/bds-tooltip.ts +100 -5
  396. package/src/web-components/interaction/form/BdsCheckbox.mdx +20 -1
  397. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +8 -0
  398. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +4 -3
  399. package/src/web-components/interaction/form/BdsCombobox.mdx +39 -2
  400. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +31 -0
  401. package/src/web-components/interaction/form/BdsFieldGroup.mdx +8 -3
  402. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  403. package/src/web-components/interaction/form/BdsFileInput.mdx +26 -5
  404. package/src/web-components/interaction/form/BdsFormInput.mdx +16 -1
  405. package/src/web-components/interaction/form/BdsNumberInput.mdx +34 -2
  406. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +27 -3
  407. package/src/web-components/interaction/form/BdsRadio.mdx +39 -12
  408. package/src/web-components/interaction/form/BdsRadio.stories.tsx +26 -0
  409. package/src/web-components/interaction/form/BdsRadioGroup.mdx +9 -4
  410. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +37 -11
  411. package/src/web-components/interaction/form/BdsSelect.mdx +22 -4
  412. package/src/web-components/interaction/form/BdsSelect.stories.tsx +33 -0
  413. package/src/web-components/interaction/form/BdsSlider.mdx +36 -19
  414. package/src/web-components/interaction/form/BdsSlider.stories.tsx +24 -0
  415. package/src/web-components/interaction/form/BdsSwitch.mdx +38 -11
  416. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +16 -0
  417. package/src/web-components/interaction/form/BdsTextarea.mdx +42 -16
  418. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +21 -0
  419. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +99 -0
  420. package/src/web-components/interaction/form/bds-checkbox-group.ts +95 -9
  421. package/src/web-components/interaction/form/bds-checkbox.spec.ts +55 -0
  422. package/src/web-components/interaction/form/bds-checkbox.ts +30 -5
  423. package/src/web-components/interaction/form/bds-combobox.spec.ts +79 -0
  424. package/src/web-components/interaction/form/bds-combobox.ts +102 -17
  425. package/src/web-components/interaction/form/bds-field-group.spec.ts +158 -0
  426. package/src/web-components/interaction/form/bds-field-group.ts +167 -23
  427. package/src/web-components/interaction/form/bds-file-input.spec.ts +73 -5
  428. package/src/web-components/interaction/form/bds-file-input.ts +71 -34
  429. package/src/web-components/interaction/form/bds-form-input.spec.ts +22 -0
  430. package/src/web-components/interaction/form/bds-form-input.ts +27 -16
  431. package/src/web-components/interaction/form/bds-number-input.spec.ts +49 -0
  432. package/src/web-components/interaction/form/bds-number-input.ts +118 -29
  433. package/src/web-components/interaction/form/bds-radio-group.spec.ts +57 -0
  434. package/src/web-components/interaction/form/bds-radio-group.ts +60 -12
  435. package/src/web-components/interaction/form/bds-radio.spec.ts +123 -0
  436. package/src/web-components/interaction/form/bds-radio.ts +136 -23
  437. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +55 -0
  438. package/src/web-components/interaction/form/bds-segmented-control.ts +81 -59
  439. package/src/web-components/interaction/form/bds-select.spec.ts +57 -0
  440. package/src/web-components/interaction/form/bds-select.ts +96 -8
  441. package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
  442. package/src/web-components/interaction/form/bds-slider.ts +181 -33
  443. package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
  444. package/src/web-components/interaction/form/bds-switch.ts +142 -41
  445. package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
  446. package/src/web-components/interaction/form/bds-textarea.ts +109 -8
  447. package/src/web-components/layout/BdsGrid.mdx +6 -2
  448. package/src/web-components/layout/BdsGrid.stories.tsx +4 -0
  449. package/src/web-components/layout/bds-grid-item.spec.ts +21 -0
  450. package/src/web-components/layout/bds-grid-item.ts +17 -3
  451. package/src/web-components/layout/bds-grid.spec.ts +98 -0
  452. package/src/web-components/layout/bds-grid.ts +139 -19
  453. package/src/web-components/ui/BdsAlert.mdx +6 -5
  454. package/src/web-components/ui/BdsAlert.stories.tsx +6 -6
  455. package/src/web-components/ui/BdsAvatar.mdx +4 -2
  456. package/src/web-components/ui/BdsBreadcrumb.mdx +3 -2
  457. package/src/web-components/ui/BdsButtonContainer.mdx +64 -0
  458. package/src/web-components/ui/BdsButtonContainer.stories.tsx +84 -0
  459. package/src/web-components/ui/BdsCalendar.mdx +29 -4
  460. package/src/web-components/ui/BdsCalendar.stories.tsx +16 -18
  461. package/src/web-components/ui/BdsCalendarRange.mdx +71 -0
  462. package/src/web-components/ui/BdsCalendarRange.stories.tsx +31 -0
  463. package/src/web-components/ui/BdsCard.mdx +7 -3
  464. package/src/web-components/ui/BdsCard.stories.tsx +17 -1
  465. package/src/web-components/ui/BdsCarousel.mdx +40 -12
  466. package/src/web-components/ui/BdsDescriptionList.mdx +6 -1
  467. package/src/web-components/ui/BdsIconWrapper.mdx +3 -2
  468. package/src/web-components/ui/BdsLink.mdx +14 -2
  469. package/src/web-components/ui/BdsNotificationBanner.mdx +18 -0
  470. package/src/web-components/ui/BdsPagination.mdx +40 -1
  471. package/src/web-components/ui/BdsProgress.mdx +8 -2
  472. package/src/web-components/ui/BdsProgressCircle.mdx +5 -2
  473. package/src/web-components/ui/BdsRating.mdx +7 -0
  474. package/src/web-components/ui/BdsSectionHeader.mdx +22 -3
  475. package/src/web-components/ui/BdsSectionHeader.stories.tsx +15 -13
  476. package/src/web-components/ui/BdsTable.mdx +23 -2
  477. package/src/web-components/ui/BdsTable.stories.tsx +8 -2
  478. package/src/web-components/ui/BdsTypography.mdx +17 -1
  479. package/src/web-components/ui/bds-alert.spec.ts +17 -2
  480. package/src/web-components/ui/bds-alert.ts +31 -5
  481. package/src/web-components/ui/bds-avatar.spec.ts +29 -0
  482. package/src/web-components/ui/bds-avatar.ts +30 -12
  483. package/src/web-components/ui/bds-badge.spec.ts +5 -0
  484. package/src/web-components/ui/bds-badge.ts +11 -1
  485. package/src/web-components/ui/bds-breadcrumb.spec.ts +30 -0
  486. package/src/web-components/ui/bds-breadcrumb.ts +26 -4
  487. package/src/web-components/ui/bds-button-container.spec.ts +39 -0
  488. package/src/web-components/ui/bds-button-container.ts +123 -0
  489. package/src/web-components/ui/bds-button-group.spec.ts +14 -20
  490. package/src/web-components/ui/bds-button-group.ts +13 -64
  491. package/src/web-components/ui/bds-calendar-range.spec.ts +123 -0
  492. package/src/web-components/ui/bds-calendar-range.ts +307 -0
  493. package/src/web-components/ui/bds-calendar.spec.ts +176 -0
  494. package/src/web-components/ui/bds-calendar.ts +186 -344
  495. package/src/web-components/ui/bds-card.spec.ts +32 -0
  496. package/src/web-components/ui/bds-card.ts +34 -9
  497. package/src/web-components/ui/bds-carousel.spec.ts +147 -0
  498. package/src/web-components/ui/bds-carousel.ts +113 -36
  499. package/src/web-components/ui/bds-description-list.spec.ts +15 -0
  500. package/src/web-components/ui/bds-description-list.ts +37 -3
  501. package/src/web-components/ui/bds-icon-wrapper.spec.ts +12 -0
  502. package/src/web-components/ui/bds-icon-wrapper.ts +16 -15
  503. package/src/web-components/ui/bds-link.spec.ts +17 -0
  504. package/src/web-components/ui/bds-link.ts +26 -11
  505. package/src/web-components/ui/bds-loading.ts +11 -1
  506. package/src/web-components/ui/bds-notification-banner.spec.ts +13 -0
  507. package/src/web-components/ui/bds-notification-banner.ts +18 -1
  508. package/src/web-components/ui/bds-pagination.spec.ts +44 -0
  509. package/src/web-components/ui/bds-pagination.ts +39 -19
  510. package/src/web-components/ui/bds-progress-circle.spec.ts +21 -0
  511. package/src/web-components/ui/bds-progress-circle.ts +17 -5
  512. package/src/web-components/ui/bds-progress.spec.ts +21 -0
  513. package/src/web-components/ui/bds-progress.ts +19 -6
  514. package/src/web-components/ui/bds-rating.spec.ts +6 -0
  515. package/src/web-components/ui/bds-rating.ts +12 -2
  516. package/src/web-components/ui/bds-section-header.spec.ts +39 -8
  517. package/src/web-components/ui/bds-section-header.ts +47 -13
  518. package/src/web-components/ui/bds-separator.ts +10 -0
  519. package/src/web-components/ui/bds-skeleton.ts +10 -0
  520. package/src/web-components/ui/bds-table.spec.ts +63 -0
  521. package/src/web-components/ui/bds-table.ts +88 -28
  522. package/src/web-components/ui/bds-typography.spec.ts +47 -0
  523. package/src/web-components/ui/bds-typography.ts +28 -10
  524. package/src/web-components/ui/calendar-styles.ts +94 -0
  525. package/dist/web-components/chunk-5TFWOPLH.js +0 -49
  526. package/dist/web-components/chunk-7FMR53BW.js +0 -127
  527. package/dist/web-components/chunk-AGKJTMJI.js +0 -56
  528. package/dist/web-components/chunk-BQLZ7I4G.js +0 -56
  529. package/dist/web-components/chunk-IYGF6CD5.js +0 -118
  530. package/dist/web-components/chunk-QBB6ZX4R.js +0 -107
  531. package/dist/web-components/chunk-Y3POAKGS.js +0 -133
  532. package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
  533. package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
  534. package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
  535. package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
@@ -2,7 +2,7 @@ import { useState, useEffect, createContext, useContext, ReactNode, useCallback,
2
2
  import css from './Toast.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
 
5
- type ToastVariant = 'success' | 'error' | 'info';
5
+ type ToastVariant = 'success' | 'error' | 'info' | 'warning';
6
6
 
7
7
  interface Toast {
8
8
  id: string;
@@ -37,13 +37,15 @@ export function ToastProvider({ children }: { children: ReactNode }) {
37
37
  }, []);
38
38
 
39
39
  // Sync the popover's top-layer visibility with the toast queue.
40
- // showPopover() is a no-op when already open (safe to call multiple times).
41
- // hidePopover() throws InvalidStateError when already hidden, so catch silently.
40
+ // Both showPopover() and hidePopover() throw an InvalidStateError when called
41
+ // in a redundant state (e.g. showPopover() while a 2nd/3rd toast is queued and
42
+ // the popover is already open, or hidePopover() before any toast has shown) —
43
+ // catch silently in both directions.
42
44
  useEffect(() => {
43
45
  const container = containerRef.current;
44
46
  if (!container) return;
45
47
  if (toasts.length > 0) {
46
- container.showPopover?.();
48
+ try { container.showPopover?.(); } catch { /* already open */ }
47
49
  } else {
48
50
  try { container.hidePopover?.(); } catch { /* already hidden on initial mount */ }
49
51
  }
@@ -65,7 +67,7 @@ export function ToastProvider({ children }: { children: ReactNode }) {
65
67
  <ToastItem
66
68
  key={toast.id}
67
69
  toast={toast}
68
- onRemove={() => removeToast(toast.id)}
70
+ onRemove={removeToast}
69
71
  />
70
72
  ))}
71
73
  </div>
@@ -73,16 +75,20 @@ export function ToastProvider({ children }: { children: ReactNode }) {
73
75
  );
74
76
  }
75
77
 
76
- function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: () => void }) {
78
+ // `onRemove` is the stable `removeToast` callback (identity never changes across
79
+ // renders) and `toast.id` never changes for a given ToastItem instance, so this
80
+ // effect mounts once per toast and is never re-triggered by sibling toasts
81
+ // joining/leaving the queue — each toast auto-dismisses on its own 5s schedule.
82
+ function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: (id: string) => void }) {
77
83
  useEffect(() => {
78
- const timer = setTimeout(onRemove, 5000);
84
+ const timer = setTimeout(() => onRemove(toast.id), 5000);
79
85
  return () => clearTimeout(timer);
80
- }, [onRemove]);
86
+ }, [onRemove, toast.id]);
81
87
 
82
88
  return (
83
89
  <div className={cn(css.toast, css[`--variant_${toast.variant}`])} role="status" aria-live="polite" aria-atomic="true">
84
90
  <span className={css.message}>{toast.message}</span>
85
- <button type="button" className={css.closeButton} onClick={onRemove} aria-label="Dismiss">
91
+ <button type="button" className={css.closeButton} onClick={() => onRemove(toast.id)} aria-label="Dismiss">
86
92
  <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
87
93
  <path strokeLinecap="round" strokeLinejoin="round" d="M18 6L6 18M6 6l12 12" />
88
94
  </svg>
@@ -46,6 +46,9 @@ Binary toggle for opt-in/opt-out choices. Renders a styled native `<input type="
46
46
  <tbody>
47
47
  <tr><td>`--checkbox_color-active`</td><td>`var(--bdc-color_active)`</td><td>Checked background</td></tr>
48
48
  <tr><td>`--checkbox_color-on-active`</td><td>`var(--bdc-color_on-active)`</td><td>Checkmark colour</td></tr>
49
+ <tr><td>`--checkbox_size`</td><td>`calc(var(--bds-space_s) * 2)`</td><td>Checkbox width/height</td></tr>
50
+ <tr><td>`--checkbox_gap`</td><td>`var(--bds-space_m)`</td><td>Gap between the checkbox and its label</td></tr>
51
+ <tr><td>`--checkbox_offset`</td><td>`0.25em`</td><td>Vertical nudge to align the checkbox with the first line of the label</td></tr>
49
52
  </tbody>
50
53
  </table>
51
54
 
@@ -1,6 +1,7 @@
1
1
  @layer boostdev.component {
2
2
  .checkboxGroup {
3
- --inputSize: calc(var(--bds-space_s) * 2);
3
+ --checkbox_size: calc(var(--bds-space_s) * 2);
4
+ --inputSize: var(--checkbox_size);
4
5
 
5
6
  display: flex;
6
7
  flex-direction: column;
@@ -9,13 +10,13 @@
9
10
  .inputWrapper {
10
11
  display: flex;
11
12
  align-items: stretch;
12
- gap: var(--bds-space_xs);
13
+ gap: var(--checkbox_gap, var(--bds-space_m));
13
14
  }
14
15
 
15
16
  .checkbox {
16
- margin-block-start: 0.25em;
17
- inline-size: var(--inputSize);
18
- block-size: var(--inputSize);
17
+ margin-block-start: var(--checkbox_offset, 0.25em);
18
+ inline-size: var(--checkbox_size);
19
+ block-size: var(--checkbox_size);
19
20
  color: var(--checkbox_color, var(--bds-color_on-bg));
20
21
  border: var(--checkbox_border, none);
21
22
  --bdc_color: currentcolor;
@@ -27,7 +28,8 @@
27
28
  background-color: var(--checkbox_color_bg, var(--bds-color_bg));
28
29
  cursor: pointer;
29
30
  position: relative;
30
- transition: --bdc_color 0.3s ease, background-color 0.3s ease;
31
+ transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing),
32
+ background-color var(--bds-animation_transition-duration) var(--bds-animation_easing);
31
33
  }
32
34
 
33
35
  .checkbox:checked {
@@ -56,6 +58,11 @@
56
58
  outline-offset: var(--bds-outline_offset);
57
59
  }
58
60
 
61
+ .checkbox:disabled {
62
+ opacity: 0.5;
63
+ cursor: not-allowed;
64
+ }
65
+
59
66
  .checkboxError {
60
67
  --bdc_color: var(--bdc_color--error);
61
68
  }
@@ -36,4 +36,10 @@ describe('Checkbox', () => {
36
36
  render(<Checkbox label="Disabled" name="disabled" disabled />);
37
37
  expect(screen.getByRole('checkbox')).toBeDisabled();
38
38
  });
39
+
40
+ it('keeps label association when a conflicting id prop is passed', () => {
41
+ render(<Checkbox label="Accept terms" name="terms" id="consumer-supplied-id" />);
42
+ const checkbox = screen.getByLabelText('Accept terms');
43
+ expect(checkbox.id).not.toBe('consumer-supplied-id');
44
+ });
39
45
  });
@@ -23,13 +23,13 @@ export function Checkbox({ label, name, error, hint, className, ...props }: Chec
23
23
  <InputContainer className={cn(css.checkboxGroup, className)}>
24
24
  <div className={css.inputWrapper}>
25
25
  <input
26
+ {...props}
26
27
  aria-describedby={describedBy}
27
28
  aria-invalid={!!error}
28
29
  type="checkbox"
29
30
  id={id}
30
31
  name={name}
31
32
  className={cn(css.checkbox, error && css.checkboxError)}
32
- {...props}
33
33
  />
34
34
  <Label id={id} label={label} />
35
35
  </div>
@@ -36,9 +36,24 @@ Groups multiple `Checkbox` inputs under a shared label. Renders a `<fieldset>` w
36
36
 
37
37
  <ArgTypes of={Stories} />
38
38
 
39
+ ## CSS variables
40
+
41
+ All public override tokens. Set on the CheckboxGroup or any ancestor; they cascade.
42
+
43
+ | Variable | Default | Description |
44
+ |----------|---------|-------------|
45
+ | `--checkboxGroup_gap` | `var(--bds-space_xs)` | Gap between the legend/items block and, via `--checkboxGroup_items-gap`, between the child checkboxes. |
46
+ | `--checkboxGroup_items-gap` | `var(--bds-space_xs)` | Gap between the child checkboxes. |
47
+ | `--checkboxGroup_legend-color` | `var(--bds-color_on-bg)` | Legend text colour. |
48
+ | `--checkboxGroup_legend-font-family` | `var(--bds-font_family--body)` | Legend font family. |
49
+ | `--checkboxGroup_legend-font-size` | `var(--bds-font_size--body--s)` | Legend font size. |
50
+ | `--checkboxGroup_legend-font-weight` | `var(--bds-font_weight--medium)` | Legend font weight. |
51
+ | `--checkboxGroup_legend-gap` | `var(--bds-space_xs)` | Space below the legend before the items. |
52
+ | `--checkboxGroup_required-color` | `var(--bds-color_error)` | Colour of the visible `*` required marker. |
53
+
39
54
  ## Accessibility
40
55
 
41
56
  - Wraps checkboxes in a `<fieldset>` with a `<legend>` — screen readers announce the group label before each option
42
57
  - `disabled` on the group sets the HTML `disabled` attribute on the `<fieldset>`, which disables all child inputs
43
- - `aria-required` is set on the `<fieldset>` when `required` is true
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
44
59
  - Error and hint messages are linked via `aria-describedby`
@@ -5,25 +5,41 @@
5
5
  margin: 0;
6
6
  display: flex;
7
7
  flex-direction: column;
8
- gap: var(--bds-space_xs);
8
+ gap: var(--checkboxGroup_gap, var(--bds-space_xs));
9
9
  }
10
10
 
11
11
  .legend {
12
- font-family: var(--bds-font_family--body);
13
- font-size: var(--bds-font_size--body--s);
14
- font-weight: var(--bds-font_weight--medium);
15
- color: var(--bds-color_on-bg);
12
+ font-family: var(--checkboxGroup_legend-font-family, var(--bds-font_family--body));
13
+ font-size: var(--checkboxGroup_legend-font-size, var(--bds-font_size--body--s));
14
+ font-weight: var(--checkboxGroup_legend-font-weight, var(--bds-font_weight--medium));
15
+ color: var(--checkboxGroup_legend-color, var(--bds-color_on-bg));
16
16
  padding: 0;
17
- margin-block-end: var(--bds-space_xs);
17
+ margin-block-end: var(--checkboxGroup_legend-gap, var(--bds-space_xs));
18
18
  }
19
19
 
20
20
  .required {
21
- color: var(--bds-color_error);
21
+ color: var(--checkboxGroup_required-color, var(--bds-color_error));
22
+ }
23
+
24
+ /* Visually hidden but still exposed to assistive tech — carries the
25
+ "required" state into the fieldset's accessible name, since the
26
+ visible asterisk above is aria-hidden and `aria-required` is not
27
+ supported ARIA on a fieldset (role=group). */
28
+ .requiredLabel {
29
+ position: absolute;
30
+ inline-size: 1px;
31
+ block-size: 1px;
32
+ padding: 0;
33
+ margin: -1px;
34
+ overflow: hidden;
35
+ clip: rect(0, 0, 0, 0);
36
+ white-space: nowrap;
37
+ border-width: 0;
22
38
  }
23
39
 
24
40
  .items {
25
41
  display: flex;
26
42
  flex-direction: column;
27
- gap: var(--bds-space_xs);
43
+ gap: var(--checkboxGroup_items-gap, var(--bds-space_xs));
28
44
  }
29
45
  }
@@ -66,16 +66,46 @@ describe('CheckboxGroup', () => {
66
66
  expect(fieldset.getAttribute('aria-describedby')).toContain(hintMsg.id);
67
67
  });
68
68
 
69
- it('marks fieldset as required when required prop is set', () => {
69
+ it('does not set the unsupported aria-required attribute on the fieldset', () => {
70
+ render(
71
+ <CheckboxGroup legend="Required" required>
72
+ <Checkbox label="Option" name="opt" />
73
+ </CheckboxGroup>
74
+ );
75
+ expect(screen.getByRole('group')).not.toHaveAttribute('aria-required');
76
+ });
77
+
78
+ it('shows a visible required marker when required prop is set', () => {
70
79
  render(
71
80
  <CheckboxGroup legend="Required" required>
72
81
  <Checkbox label="Option" name="opt" />
73
82
  </CheckboxGroup>
74
83
  );
75
- expect(screen.getByRole('group')).toHaveAttribute('aria-required', 'true');
76
84
  expect(screen.getByText('*')).toBeInTheDocument();
77
85
  });
78
86
 
87
+ it('conveys required state to assistive tech via the accessible group name', () => {
88
+ render(
89
+ <CheckboxGroup legend="Contact preferences" required>
90
+ <Checkbox label="Option" name="opt" />
91
+ </CheckboxGroup>
92
+ );
93
+ expect(
94
+ screen.getByRole('group', { name: 'Contact preferences (required)' })
95
+ ).toBeInTheDocument();
96
+ });
97
+
98
+ it('omits the required marker and accessible required text when required is not set', () => {
99
+ render(
100
+ <CheckboxGroup legend="Optional preferences">
101
+ <Checkbox label="Option" name="opt" />
102
+ </CheckboxGroup>
103
+ );
104
+ expect(screen.queryByText('*')).not.toBeInTheDocument();
105
+ expect(screen.queryByText('(required)')).not.toBeInTheDocument();
106
+ expect(screen.getByRole('group', { name: 'Optional preferences' })).toBeInTheDocument();
107
+ });
108
+
79
109
  it('disables the fieldset when disabled prop is set', () => {
80
110
  render(
81
111
  <CheckboxGroup legend="Disabled" disabled>
@@ -29,12 +29,24 @@ export function CheckboxGroup({
29
29
  <fieldset
30
30
  {...rest}
31
31
  className={cn(css.group, className)}
32
- aria-required={required || undefined}
33
32
  aria-describedby={describedBy}
34
33
  >
35
34
  <legend className={css.legend}>
36
35
  {legend}
37
- {required && <span className={css.required} aria-hidden="true"> *</span>}
36
+ {required && (
37
+ <>
38
+ {/* Visible-only marker — aria-hidden because `aria-required` is not
39
+ supported ARIA on a fieldset/role=group, so requiredness must be
40
+ conveyed through the legend's accessible text instead (below). The
41
+ literal `{' '}` text node sits directly in the legend (not nested
42
+ inside a span) so it survives accessible-name computation — a
43
+ leading space nested inside an element gets trimmed away when
44
+ that element's own text alternative is computed. */}
45
+ <span className={css.required} aria-hidden="true"> *</span>
46
+ {' '}
47
+ <span className={css.requiredLabel}>(required)</span>
48
+ </>
49
+ )}
38
50
  </legend>
39
51
  <div className={css.items}>{children}</div>
40
52
  <Message inputId={id} type="error" message={error} />
@@ -42,10 +42,16 @@ Searchable select with type-ahead filtering. Combines a text input with a dropdo
42
42
  <tr><td>`--combobox_border`</td><td>`none`</td><td>Input border</td></tr>
43
43
  <tr><td>`--combobox_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Input corner radius</td></tr>
44
44
  <tr><td>`--combobox_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Input box shadow</td></tr>
45
- <tr><td>`--combobox_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Dropdown list text colour</td></tr>
45
+ <tr><td>`--combobox_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Dropdown list and option text colour</td></tr>
46
+ <tr><td>`--combobox_listbox-bg`</td><td>`var(--combobox_color_bg)`</td><td>Dropdown list background colour</td></tr>
46
47
  <tr><td>`--combobox_listbox-border`</td><td>`none`</td><td>Dropdown list border</td></tr>
47
48
  <tr><td>`--combobox_listbox-radius`</td><td>`var(--bdc-outline_radius)`</td><td>Dropdown list corner radius</td></tr>
48
49
  <tr><td>`--combobox_listbox-shadow`</td><td>`var(--bds-shadow_m)`</td><td>Dropdown list box shadow</td></tr>
50
+ <tr><td>`--combobox_listbox-max-block-size`</td><td>`15rem`</td><td>Dropdown list maximum height</td></tr>
51
+ <tr><td>`--combobox_listbox-padding-block`</td><td>`var(--bds-space_xxs)`</td><td>Dropdown list block padding</td></tr>
52
+ <tr><td>`--combobox_option-padding-block`</td><td>`var(--bds-space_xs)`</td><td>Option block padding</td></tr>
53
+ <tr><td>`--combobox_option-padding-inline`</td><td>`var(--bds-space_m)`</td><td>Option inline padding</td></tr>
54
+ <tr><td>`--combobox_option-font-size`</td><td>`var(--bds-font_size--body)`</td><td>Option font size</td></tr>
49
55
  <tr><td>`--combobox_option-bg--highlighted`</td><td>`var(--bds-color_bg--subtle)`</td><td>Highlighted option background</td></tr>
50
56
  <tr><td>`--combobox_option-color--highlighted`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Highlighted option text colour</td></tr>
51
57
  </tbody>
@@ -55,6 +61,7 @@ Searchable select with type-ahead filtering. Combines a text input with a dropdo
55
61
  ## Accessibility
56
62
 
57
63
  - Input uses `role="combobox"` with `aria-autocomplete="list"` and `aria-controls`
58
- - Listbox uses `role="listbox"` with `role="option"` items
59
- - `aria-activedescendant` tracks keyboard-highlighted option
60
- - `Escape` closes the dropdown; `Enter` selects the active option
64
+ - Listbox uses `role="listbox"` with `role="option"` items, and gets an accessible name via `aria-label` derived from the `label` prop when it is a string
65
+ - `aria-activedescendant` tracks the keyboard-highlighted option and is only set while the listbox is open
66
+ - `ArrowDown`/`ArrowUp` open the closed listbox on first press, then move the highlight to the next/previous enabled option (disabled options are skipped)
67
+ - `Escape` closes the dropdown; `Enter` selects the highlighted option (a no-op on a disabled option)
@@ -66,17 +66,17 @@
66
66
  inset-block-start: calc(100% + var(--bds-space_xxs));
67
67
  inset-inline: 0;
68
68
  z-index: var(--bds-z-index_dropdown);
69
- max-block-size: 15rem;
69
+ max-block-size: var(--combobox_listbox-max-block-size, 15rem);
70
70
  overflow-block: auto;
71
71
  list-style: none;
72
72
  margin: 0;
73
- padding: var(--bds-space_xxs) 0;
73
+ padding: var(--combobox_listbox-padding-block, var(--bds-space_xxs)) 0;
74
74
  border: var(--combobox_listbox-border, none);
75
75
  --bdc_color: currentcolor;
76
76
  --bdc-outline_radius: var(--bds-border_radius--s);
77
77
 
78
78
  border-radius: var(--combobox_listbox-radius, var(--bdc-outline_radius));
79
- background-color: var(--combobox_color, var(--bds-color_bg));
79
+ background-color: var(--combobox_listbox-bg, var(--combobox_color_bg, var(--bds-color_bg)));
80
80
  color: var(--combobox_on-color, var(--bds-color_on-bg));
81
81
  box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--combobox_listbox-shadow, var(--bds-shadow_m));
82
82
  }
@@ -84,9 +84,9 @@
84
84
  .option {
85
85
  display: flex;
86
86
  align-items: center;
87
- padding: var(--bds-space_xs) var(--bds-space_m);
88
- font-size: var(--bds-font_size--body);
89
- color: var(--combobox_color, var(--bds-color_on-bg));
87
+ padding: var(--combobox_option-padding-block, var(--bds-space_xs)) var(--combobox_option-padding-inline, var(--bds-space_m));
88
+ font-size: var(--combobox_option-font-size, var(--bds-font_size--body));
89
+ color: var(--combobox_on-color, var(--bds-color_on-bg));
90
90
  cursor: pointer;
91
91
  transition: var(--bds-animation_transition);
92
92
  }
@@ -78,4 +78,51 @@ describe('Combobox', () => {
78
78
  render(<Combobox label="Country" name="country" options={options} error="Required" />);
79
79
  expect(screen.getByRole('combobox')).toHaveAttribute('aria-invalid', 'true');
80
80
  });
81
+
82
+ it('gives the listbox an accessible name from the label', async () => {
83
+ const user = userEvent.setup();
84
+ render(<Combobox label="Country" name="country" options={options} />);
85
+ await user.click(screen.getByRole('combobox'));
86
+ expect(screen.getByRole('listbox')).toHaveAccessibleName('Country');
87
+ });
88
+
89
+ it('opens the closed listbox on ArrowUp without setting aria-activedescendant', async () => {
90
+ const user = userEvent.setup();
91
+ render(<Combobox label="Country" name="country" options={options} />);
92
+ const combobox = screen.getByRole('combobox');
93
+ await user.click(combobox);
94
+ await user.keyboard('{Escape}');
95
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
96
+ await user.keyboard('{ArrowUp}');
97
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
98
+ expect(combobox).not.toHaveAttribute('aria-activedescendant');
99
+ });
100
+
101
+ it('does not select a disabled option on Enter when it is the only filtered result', async () => {
102
+ const user = userEvent.setup();
103
+ const onChange = vi.fn();
104
+ render(<Combobox label="Country" name="country" options={options} onChange={onChange} />);
105
+ await user.type(screen.getByRole('combobox'), 'France');
106
+ expect(screen.getByRole('option', { name: 'France' })).toBeInTheDocument();
107
+ await user.keyboard('{ArrowDown}');
108
+ await user.keyboard('{Enter}');
109
+ expect(onChange).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('clears aria-activedescendant when the listbox closes via an outside click', async () => {
113
+ const user = userEvent.setup();
114
+ render(
115
+ <div>
116
+ <Combobox label="Country" name="country" options={options} />
117
+ <button type="button">Outside</button>
118
+ </div>
119
+ );
120
+ const combobox = screen.getByRole('combobox');
121
+ await user.click(combobox);
122
+ await user.keyboard('{ArrowDown}');
123
+ expect(combobox).toHaveAttribute('aria-activedescendant');
124
+ await user.click(screen.getByRole('button', { name: 'Outside' }));
125
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
126
+ expect(combobox).not.toHaveAttribute('aria-activedescendant');
127
+ });
81
128
  });
@@ -102,18 +102,22 @@ export function Combobox({
102
102
  if (e.key === 'ArrowDown') {
103
103
  e.preventDefault();
104
104
  if (!isOpen) { setIsOpen(true); return; }
105
+ if (enabledIndexes.length === 0) { setHighlightedIndex(-1); return; }
105
106
  const pos = enabledIndexes.indexOf(highlightedIndex);
106
- setHighlightedIndex(enabledIndexes[(pos + 1) % enabledIndexes.length] ?? 0);
107
+ setHighlightedIndex(enabledIndexes[(pos + 1) % enabledIndexes.length]);
107
108
  } else if (e.key === 'ArrowUp') {
108
109
  e.preventDefault();
110
+ if (!isOpen) { setIsOpen(true); return; }
111
+ if (enabledIndexes.length === 0) { setHighlightedIndex(-1); return; }
109
112
  const pos = enabledIndexes.indexOf(highlightedIndex);
110
113
  setHighlightedIndex(
111
- enabledIndexes[(pos - 1 + enabledIndexes.length) % enabledIndexes.length] ?? 0
114
+ enabledIndexes[(pos - 1 + enabledIndexes.length) % enabledIndexes.length]
112
115
  );
113
116
  } else if (e.key === 'Enter') {
114
117
  e.preventDefault();
115
- if (highlightedIndex >= 0 && filtered[highlightedIndex]) {
116
- selectOption(filtered[highlightedIndex]);
118
+ const option = filtered[highlightedIndex];
119
+ if (highlightedIndex >= 0 && option && !option.disabled) {
120
+ selectOption(option);
117
121
  }
118
122
  } else if (e.key === 'Escape') {
119
123
  setIsOpen(false);
@@ -137,7 +141,7 @@ export function Combobox({
137
141
  aria-haspopup="listbox"
138
142
  aria-autocomplete="list"
139
143
  aria-controls={listboxId}
140
- aria-activedescendant={highlightedIndex >= 0 ? getOptionId(highlightedIndex) : undefined}
144
+ aria-activedescendant={isOpen && highlightedIndex >= 0 ? getOptionId(highlightedIndex) : undefined}
141
145
  aria-invalid={!!error}
142
146
  aria-describedby={describedBy}
143
147
  autoComplete="off"
@@ -158,6 +162,7 @@ export function Combobox({
158
162
  <ul
159
163
  id={listboxId}
160
164
  role="listbox"
165
+ aria-label={typeof label === 'string' ? label : undefined}
161
166
  className={css.listbox}
162
167
  >
163
168
  {filtered.map((option, index) => (
@@ -5,7 +5,7 @@ import * as Stories from './FieldGroup.stories';
5
5
 
6
6
  # FieldGroup
7
7
 
8
- Semantically groups related form fields under a single `<fieldset>` and lays them out either horizontally (with a CSS container query that collapses to a vertical stack when the FieldGroup itself is too narrow) or vertically.
8
+ Semantically groups related form fields under a single `<fieldset>` and lays them out either horizontally (collapsing to a vertical stack when the FieldGroup itself is too narrow) or vertically.
9
9
 
10
10
  Use this any time multiple fields belong to the same conceptual section — contact details, address, search filters, registration step — and you want them grouped for screen readers AND visually arranged on a single row when there's room.
11
11
 
@@ -23,13 +23,13 @@ Use this any time multiple fields belong to the same conceptual section — cont
23
23
 
24
24
  ## Variants
25
25
 
26
- - **`horizontal`** (default) — fields flow into a row; each claims at least `--fieldGroup_min-field-width`. When the FieldGroup's own inline-size drops below the **auto-derived** threshold (`N × min-field-width + (N − 1) × gap`, where `N` is the actual number of children), the layout collapses to a single column via a CSS container query. A 2-field group collapses earlier than a 5-field group; you don't have to tune anything per call site.
26
+ - **`horizontal`** (default) — fields flow into a row; each claims at least `--fieldGroup_min-field-width`. When the FieldGroup's own inline-size drops below the **auto-derived** threshold (`N × min-field-width + (N − 1) × gap`, where `N` is the actual number of children), the layout collapses to a single column. A 2-field group collapses earlier than a 5-field group; you don't have to tune anything per call site.
27
27
  - **`vertical`** — fields always stack one per row.
28
28
 
29
29
  <Canvas of={Stories.Horizontal} />
30
30
  <Canvas of={Stories.Vertical} />
31
31
 
32
- ### Container query in action
32
+ ### Auto-collapse in action
33
33
 
34
34
  The collapse-to-vertical threshold measures the **FieldGroup itself**, not the viewport. The FieldGroup below sits inside a 20 rem wrapper, so the same horizontal variant collapses cleanly to a single column even though the page is wide.
35
35
 
@@ -77,6 +77,7 @@ All public override tokens. Set on the FieldGroup or any ancestor; they cascade.
77
77
  | Variable | Default | Description |
78
78
  |----------|---------|-------------|
79
79
  | `--fieldGroup_legend-color` | `var(--bds-color_on-bg)` | Legend text colour. |
80
+ | `--fieldGroup_legend-font-family` | `var(--bds-font_family--body)` | Legend font family. |
80
81
  | `--fieldGroup_legend-font-size` | `var(--bds-font_size--heading-3)` | Legend font size. |
81
82
  | `--fieldGroup_legend-font-weight` | `var(--bds-font_weight--semibold)` | Legend font weight. |
82
83
  | `--fieldGroup_legend-line-height` | `var(--bds-font_line-height--heading)` | Legend line height. |
@@ -88,13 +89,13 @@ All public override tokens. Set on the FieldGroup or any ancestor; they cascade.
88
89
 
89
90
  - Renders as `<fieldset>` so assistive tech announces the legend before each field, and the native `disabled` attribute cascades to all descendants.
90
91
  - When `legend` is omitted, you must provide `aria-label` or `aria-labelledby` on the FieldGroup so the group still has an accessible name.
91
- - The container query is a layout-only change — DOM order stays in source order, so the tab/reading order is unaffected by the horizontal-to-vertical switch.
92
+ - The auto-collapse is a layout-only change — DOM order stays in source order, so the tab/reading order is unaffected by the horizontal-to-vertical switch.
92
93
 
93
94
  ## How the responsive layout works
94
95
 
95
96
  The horizontal variant uses `display: flex; flex-flow: row wrap;` with each child claiming at least `--fieldGroup_min-field-width`. This handles natural wrapping across multiple rows.
96
97
 
97
- Layered on top, a CSS [`@container`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@container) query forces a clean single-column layout when the FieldGroup itself is narrower than the **auto-derived** collapse threshold:
98
+ Layered on top, FieldGroup forces a clean single-column layout when the FieldGroup itself is narrower than an **auto-derived** collapse threshold:
98
99
 
99
100
  ```css
100
101
  --fieldGroup_collapse-threshold:
@@ -103,11 +104,9 @@ Layered on top, a CSS [`@container`](https://developer.mozilla.org/en-US/docs/We
103
104
  + (var(--fieldGroup_field-count) - 1) * var(--fieldGroup_gap)
104
105
  );
105
106
 
106
- @container field-group (max-inline-size: var(--fieldGroup_collapse-threshold)) {
107
- .fieldGroup.--variant_horizontal .fields { flex-direction: column; }
108
- }
107
+ .fieldGroup.--variant_horizontal[data-collapsed='true'] .fields { flex-direction: column; }
109
108
  ```
110
109
 
111
- `--fieldGroup_field-count` is set by the React component as an inline `style` custom property derived from `Children.count(children)`, so the threshold scales with the actual content. A 2-field group collapses around `2 × 12rem + 1 × gap`; a 5-field group collapses around `5 × 12rem + 4 × gap`. No magic numbers per call site.
110
+ `--fieldGroup_field-count` is set by the React component as an inline `style` custom property derived from the actual rendered children, so the threshold scales with the actual content. A 2-field group collapses around `2 × 12rem + 1 × gap`; a 5-field group collapses around `5 × 12rem + 4 × gap`. No magic numbers per call site.
112
111
 
113
- Because the query is bound to the FieldGroup's own inline-size (via `container-type: inline-size`), the same FieldGroup behaves correctly whether you place it in a sidebar, a modal, a 2-up grid cell, or a full-width page with zero media-query wiring at the call site.
112
+ CSS [`@container`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@container) query *conditions* can't reference custom properties (`var()` inside a container-query feature value is invalid CSS), so this per-instance threshold can't drive an `@container` rule directly. Instead, FieldGroup renders a hidden probe element whose own inline-size resolves `--fieldGroup_collapse-threshold` as a normal property value, and a `ResizeObserver` compares the probe's resolved pixel width against the fieldset's own width, toggling `data-collapsed="true"` on the fieldset when the fieldset is narrower. This keeps the same effect as a container query — bound to the FieldGroup's own inline-size, not the viewport — so the same FieldGroup behaves correctly whether you place it in a sidebar, a modal, a 2-up grid cell, or a full-width page, with zero media-query wiring at the call site. `ResizeObserver` is required for this collapse to run; it's supported by all current browsers, and in its absence (or during SSR) the layout simply stays in its declared variant.