zabi-components 7.0.2 → 8.1.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 (433) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +287 -11
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1329 -163
  5. package/dist/atoms/ActionPanel.svelte +24 -9
  6. package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
  7. package/dist/atoms/Avatar.svelte +104 -0
  8. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  9. package/dist/atoms/Badge.svelte +124 -58
  10. package/dist/atoms/Badge.svelte.d.ts +17 -5
  11. package/dist/atoms/Button.svelte +212 -62
  12. package/dist/atoms/Button.svelte.d.ts +33 -2
  13. package/dist/atoms/Card.svelte +31 -28
  14. package/dist/atoms/Card.svelte.d.ts +5 -2
  15. package/dist/atoms/CardContent.svelte +10 -2
  16. package/dist/atoms/CardContent.svelte.d.ts +2 -0
  17. package/dist/atoms/CardFooter.svelte +10 -2
  18. package/dist/atoms/CardFooter.svelte.d.ts +2 -0
  19. package/dist/atoms/CardHeader.svelte +26 -6
  20. package/dist/atoms/CardHeader.svelte.d.ts +2 -0
  21. package/dist/atoms/Checkbox.svelte +52 -21
  22. package/dist/atoms/Checkbox.svelte.d.ts +6 -2
  23. package/dist/atoms/CodeBlock.svelte +35 -9
  24. package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
  25. package/dist/atoms/ColorPicker.svelte +272 -52
  26. package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
  27. package/dist/atoms/Container.svelte +17 -10
  28. package/dist/atoms/DateField.svelte +107 -0
  29. package/dist/atoms/DateField.svelte.d.ts +53 -0
  30. package/dist/atoms/Divider.svelte +4 -4
  31. package/dist/atoms/FeatureCard.svelte +4 -3
  32. package/dist/atoms/FieldMessages.svelte +69 -0
  33. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  34. package/dist/atoms/FloatingActionButton.svelte +174 -0
  35. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  36. package/dist/atoms/Heading.svelte +93 -32
  37. package/dist/atoms/Heading.svelte.d.ts +24 -4
  38. package/dist/atoms/IconButton.svelte +242 -53
  39. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  40. package/dist/atoms/Input.svelte +267 -74
  41. package/dist/atoms/Input.svelte.d.ts +32 -2
  42. package/dist/atoms/List.svelte +13 -2
  43. package/dist/atoms/List.svelte.d.ts +2 -0
  44. package/dist/atoms/ListItem.svelte +44 -11
  45. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  46. package/dist/atoms/ListItemLeading.svelte +4 -2
  47. package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
  48. package/dist/atoms/OptimizedImage.svelte +11 -2
  49. package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
  50. package/dist/atoms/Progress.svelte +31 -9
  51. package/dist/atoms/Progress.svelte.d.ts +6 -0
  52. package/dist/atoms/Radio.svelte +23 -3
  53. package/dist/atoms/Radio.svelte.d.ts +6 -2
  54. package/dist/atoms/Rating.svelte +494 -0
  55. package/dist/atoms/Rating.svelte.d.ts +56 -0
  56. package/dist/atoms/Select.svelte +475 -135
  57. package/dist/atoms/Select.svelte.d.ts +52 -2
  58. package/dist/atoms/SelectionControl.svelte +99 -18
  59. package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
  60. package/dist/atoms/Skeleton.svelte +5 -3
  61. package/dist/atoms/Slider.svelte +309 -0
  62. package/dist/atoms/Slider.svelte.d.ts +40 -0
  63. package/dist/atoms/Spinner.svelte +57 -0
  64. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  65. package/dist/atoms/Table.svelte +152 -6
  66. package/dist/atoms/Table.svelte.d.ts +15 -3
  67. package/dist/atoms/TemporalField.svelte +140 -0
  68. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  69. package/dist/atoms/Text.svelte +56 -15
  70. package/dist/atoms/Text.svelte.d.ts +13 -4
  71. package/dist/atoms/Textarea.svelte +57 -45
  72. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  73. package/dist/atoms/ThemeToggle.svelte +238 -56
  74. package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
  75. package/dist/atoms/TimeField.svelte +108 -0
  76. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  77. package/dist/atoms/Toast.svelte +25 -10
  78. package/dist/atoms/Toast.svelte.d.ts +2 -0
  79. package/dist/atoms/Toggle.svelte +60 -19
  80. package/dist/atoms/Toggle.svelte.d.ts +18 -2
  81. package/dist/atoms/Tooltip.svelte +583 -40
  82. package/dist/atoms/Tooltip.svelte.d.ts +26 -0
  83. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  84. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  85. package/dist/atoms/index.d.ts +17 -0
  86. package/dist/atoms/index.js +12 -0
  87. package/dist/atoms/selection-control.styles.d.ts +36 -5
  88. package/dist/atoms/selection-control.styles.js +40 -9
  89. package/dist/components/atoms/index.d.ts +17 -0
  90. package/dist/components/atoms/index.d.ts.map +1 -1
  91. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  92. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  93. package/dist/components/index.d.ts +83 -3
  94. package/dist/components/index.d.ts.map +1 -1
  95. package/dist/components/molecules/index.d.ts +46 -0
  96. package/dist/components/molecules/index.d.ts.map +1 -1
  97. package/dist/components/molecules/toast-store.d.ts +53 -7
  98. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  99. package/dist/components/organisms/index.d.ts +6 -0
  100. package/dist/components/organisms/index.d.ts.map +1 -1
  101. package/dist/components/types/variants.d.ts +15 -1
  102. package/dist/components/types/variants.d.ts.map +1 -1
  103. package/dist/components/util/app-shell.d.ts +28 -0
  104. package/dist/components/util/app-shell.d.ts.map +1 -0
  105. package/dist/components/util/avatar.d.ts +36 -0
  106. package/dist/components/util/avatar.d.ts.map +1 -0
  107. package/dist/components/util/bottom-sheet.d.ts +30 -0
  108. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  109. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  110. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  111. package/dist/components/util/calendar.d.ts +56 -0
  112. package/dist/components/util/calendar.d.ts.map +1 -0
  113. package/dist/components/util/cn.d.ts +8 -0
  114. package/dist/components/util/cn.d.ts.map +1 -0
  115. package/dist/components/util/collapsible.d.ts +58 -0
  116. package/dist/components/util/collapsible.d.ts.map +1 -0
  117. package/dist/components/util/confirm-dialog.d.ts +12 -0
  118. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  119. package/dist/components/util/date.d.ts +96 -0
  120. package/dist/components/util/date.d.ts.map +1 -0
  121. package/dist/components/util/drawer.d.ts +13 -0
  122. package/dist/components/util/drawer.d.ts.map +1 -0
  123. package/dist/components/util/dropdown.d.ts +47 -0
  124. package/dist/components/util/dropdown.d.ts.map +1 -0
  125. package/dist/components/util/field.d.ts +39 -0
  126. package/dist/components/util/field.d.ts.map +1 -0
  127. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  128. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  129. package/dist/components/util/focus-utils.d.ts +75 -2
  130. package/dist/components/util/focus-utils.d.ts.map +1 -1
  131. package/dist/components/util/hydration.d.ts +45 -0
  132. package/dist/components/util/hydration.d.ts.map +1 -0
  133. package/dist/components/util/keyboard-inset.d.ts +18 -0
  134. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  135. package/dist/components/util/media-grid.d.ts +51 -0
  136. package/dist/components/util/media-grid.d.ts.map +1 -0
  137. package/dist/components/util/media.d.ts +6 -0
  138. package/dist/components/util/media.d.ts.map +1 -0
  139. package/dist/components/util/overlay-chrome.d.ts +53 -0
  140. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  141. package/dist/components/util/overlay.d.ts +72 -0
  142. package/dist/components/util/overlay.d.ts.map +1 -0
  143. package/dist/components/util/photo.d.ts +215 -0
  144. package/dist/components/util/photo.d.ts.map +1 -0
  145. package/dist/components/util/portal.d.ts +23 -0
  146. package/dist/components/util/portal.d.ts.map +1 -0
  147. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  148. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  149. package/dist/components/util/radio-keys.d.ts +25 -0
  150. package/dist/components/util/radio-keys.d.ts.map +1 -0
  151. package/dist/components/util/rating.d.ts +23 -0
  152. package/dist/components/util/rating.d.ts.map +1 -0
  153. package/dist/components/util/ready-made-strings.d.ts +77 -0
  154. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  155. package/dist/components/util/scroll-padding.d.ts +20 -0
  156. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  157. package/dist/components/util/segmented-control.d.ts +16 -0
  158. package/dist/components/util/segmented-control.d.ts.map +1 -0
  159. package/dist/components/util/select.d.ts +41 -0
  160. package/dist/components/util/select.d.ts.map +1 -0
  161. package/dist/components/util/sheet-drag.d.ts +60 -0
  162. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  163. package/dist/components/util/sidebar.d.ts +63 -0
  164. package/dist/components/util/sidebar.d.ts.map +1 -0
  165. package/dist/components/util/sortable-list.d.ts +65 -0
  166. package/dist/components/util/sortable-list.d.ts.map +1 -0
  167. package/dist/components/util/ssr-safe.d.ts +4 -4
  168. package/dist/components/util/ssr-safe.d.ts.map +1 -1
  169. package/dist/components/util/stepper.d.ts +40 -0
  170. package/dist/components/util/stepper.d.ts.map +1 -0
  171. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  172. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  173. package/dist/components/util/theme-mode.d.ts +80 -0
  174. package/dist/components/util/theme-mode.d.ts.map +1 -0
  175. package/dist/components/util/toaster.d.ts +93 -0
  176. package/dist/components/util/toaster.d.ts.map +1 -0
  177. package/dist/components/util/tooltip.d.ts +10 -0
  178. package/dist/components/util/tooltip.d.ts.map +1 -0
  179. package/dist/components/util/top-navbar.d.ts +11 -0
  180. package/dist/components/util/top-navbar.d.ts.map +1 -0
  181. package/dist/components/util/touch-target.d.ts +31 -0
  182. package/dist/components/util/touch-target.d.ts.map +1 -0
  183. package/dist/components/util/zabi-strings.d.ts +133 -0
  184. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  185. package/dist/create-theme/cli.js +169 -0
  186. package/dist/create-theme/index.d.ts +176 -0
  187. package/dist/create-theme/index.js +817 -0
  188. package/dist/create-theme/lib/ramp-math.js +128 -0
  189. package/dist/create-theme/lib/resolve.js +141 -0
  190. package/dist/create-theme/theme-data.js +1116 -0
  191. package/dist/index.d.ts +83 -3
  192. package/dist/index.js +60 -8
  193. package/dist/molecules/Alert.svelte +73 -66
  194. package/dist/molecules/Alert.svelte.d.ts +9 -1
  195. package/dist/molecules/AppBar.svelte +415 -0
  196. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  197. package/dist/molecules/AvatarGroup.svelte +116 -0
  198. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  199. package/dist/molecules/BottomSheet.svelte +540 -0
  200. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  201. package/dist/molecules/BottomTabBar.svelte +297 -0
  202. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  203. package/dist/molecules/Calendar.svelte +597 -0
  204. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  205. package/dist/molecules/Collapsible.svelte +243 -0
  206. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  207. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  208. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  209. package/dist/molecules/ComponentDemo.svelte +25 -5
  210. package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
  211. package/dist/molecules/ConfirmDialog.svelte +253 -0
  212. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  213. package/dist/molecules/ContactForm.svelte +46 -19
  214. package/dist/molecules/ContactForm.svelte.d.ts +10 -0
  215. package/dist/molecules/Drawer.svelte +394 -0
  216. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  217. package/dist/molecules/Dropdown.svelte +540 -50
  218. package/dist/molecules/Dropdown.svelte.d.ts +49 -6
  219. package/dist/molecules/DropdownItem.svelte +122 -0
  220. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  221. package/dist/molecules/EmptyState.svelte +56 -10
  222. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  223. package/dist/molecules/Form.svelte +25 -14
  224. package/dist/molecules/Form.svelte.d.ts +13 -5
  225. package/dist/molecules/FormField.svelte +11 -2
  226. package/dist/molecules/FormField.svelte.d.ts +2 -0
  227. package/dist/molecules/Header.svelte +13 -3
  228. package/dist/molecules/Header.svelte.d.ts +2 -0
  229. package/dist/molecules/ImageUpload.svelte +422 -70
  230. package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
  231. package/dist/molecules/MediaGrid.svelte +527 -0
  232. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  233. package/dist/molecules/Modal.svelte +340 -45
  234. package/dist/molecules/Modal.svelte.d.ts +59 -7
  235. package/dist/molecules/NavigationMenu.svelte +15 -3
  236. package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
  237. package/dist/molecules/NavigationMenuContent.svelte +91 -36
  238. package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
  239. package/dist/molecules/NavigationMenuItem.svelte +10 -3
  240. package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
  241. package/dist/molecules/NavigationMenuLink.svelte +10 -2
  242. package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
  243. package/dist/molecules/NavigationMenuList.svelte +19 -4
  244. package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
  245. package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
  246. package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
  247. package/dist/molecules/Page.svelte +38 -2
  248. package/dist/molecules/Page.svelte.d.ts +13 -0
  249. package/dist/molecules/PhotoGrid.svelte +433 -0
  250. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  251. package/dist/molecules/PhotoViewer.svelte +1116 -0
  252. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  253. package/dist/molecules/PropsTable.svelte +26 -11
  254. package/dist/molecules/PropsTable.svelte.d.ts +5 -0
  255. package/dist/molecules/PullToRefresh.svelte +263 -0
  256. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  257. package/dist/molecules/RadioGroup.svelte +57 -15
  258. package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
  259. package/dist/molecules/Section.svelte +13 -5
  260. package/dist/molecules/Section.svelte.d.ts +3 -1
  261. package/dist/molecules/SegmentedControl.svelte +323 -0
  262. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  263. package/dist/molecules/SidebarBrandHeader.svelte +24 -5
  264. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
  265. package/dist/molecules/SidebarFooter.svelte +39 -8
  266. package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
  267. package/dist/molecules/SidebarNavSection.svelte +12 -4
  268. package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
  269. package/dist/molecules/SlideUp.svelte +253 -30
  270. package/dist/molecules/SlideUp.svelte.d.ts +31 -4
  271. package/dist/molecules/SortableList.svelte +632 -0
  272. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  273. package/dist/molecules/Stepper.svelte +548 -0
  274. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  275. package/dist/molecules/StickyActionBar.svelte +191 -0
  276. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  277. package/dist/molecules/SwipeableListItem.svelte +334 -0
  278. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  279. package/dist/molecules/Tabs.svelte +254 -24
  280. package/dist/molecules/Tabs.svelte.d.ts +15 -4
  281. package/dist/molecules/Toaster.svelte +352 -6
  282. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  283. package/dist/molecules/ToasterToast.svelte +279 -62
  284. package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
  285. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  286. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  287. package/dist/molecules/index.d.ts +46 -0
  288. package/dist/molecules/index.js +27 -0
  289. package/dist/molecules/toast-store.d.ts +53 -7
  290. package/dist/molecules/toast-store.js +21 -0
  291. package/dist/organisms/AppShell.svelte +196 -0
  292. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  293. package/dist/organisms/SidebarAccountPanel.svelte +111 -12
  294. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
  295. package/dist/organisms/SidebarNavigation.svelte +193 -69
  296. package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
  297. package/dist/organisms/SidebarPanel.svelte +45 -16
  298. package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
  299. package/dist/organisms/SidebarShell.svelte +263 -0
  300. package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
  301. package/dist/organisms/TopNavbar.svelte +329 -33
  302. package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
  303. package/dist/organisms/index.d.ts +6 -0
  304. package/dist/organisms/index.js +4 -0
  305. package/dist/routes/lib/variant-utils.d.ts +26 -0
  306. package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
  307. package/dist/types/components.d.ts +4 -0
  308. package/dist/types/components.d.ts.map +1 -1
  309. package/dist/types/events.js +1 -0
  310. package/dist/types/index.d.ts +605 -25
  311. package/dist/types/index.d.ts.map +1 -1
  312. package/dist/types/index.js +3 -0
  313. package/dist/types/variants.ts +17 -1
  314. package/dist/util/app-shell.d.ts +27 -0
  315. package/dist/util/app-shell.js +72 -0
  316. package/dist/util/avatar.d.ts +35 -0
  317. package/dist/util/avatar.js +52 -0
  318. package/dist/util/bottom-sheet.d.ts +29 -0
  319. package/dist/util/bottom-sheet.js +43 -0
  320. package/dist/util/bottom-tab-bar.d.ts +34 -0
  321. package/dist/util/bottom-tab-bar.js +54 -0
  322. package/dist/util/calendar.d.ts +55 -0
  323. package/dist/util/calendar.js +74 -0
  324. package/dist/util/cn.d.ts +7 -0
  325. package/dist/util/cn.js +48 -0
  326. package/dist/util/collapsible.d.ts +57 -0
  327. package/dist/util/collapsible.js +23 -0
  328. package/dist/util/confirm-dialog.d.ts +11 -0
  329. package/dist/util/confirm-dialog.js +2 -0
  330. package/dist/util/date.d.ts +95 -0
  331. package/dist/util/date.js +237 -0
  332. package/dist/util/drawer.d.ts +12 -0
  333. package/dist/util/drawer.js +7 -0
  334. package/dist/util/dropdown.d.ts +46 -0
  335. package/dist/util/dropdown.js +21 -0
  336. package/dist/util/field.d.ts +38 -0
  337. package/dist/util/field.js +38 -0
  338. package/dist/util/fit-in-viewport.d.ts +200 -0
  339. package/dist/util/fit-in-viewport.js +399 -0
  340. package/dist/util/focus-utils.d.ts +75 -2
  341. package/dist/util/focus-utils.js +182 -9
  342. package/dist/util/hydration.d.ts +44 -0
  343. package/dist/util/hydration.js +58 -0
  344. package/dist/util/keyboard-inset.d.ts +17 -0
  345. package/dist/util/keyboard-inset.js +53 -0
  346. package/dist/util/media-grid.d.ts +50 -0
  347. package/dist/util/media-grid.js +81 -0
  348. package/dist/util/media.d.ts +5 -0
  349. package/dist/util/media.js +10 -0
  350. package/dist/util/overlay-chrome.d.ts +52 -0
  351. package/dist/util/overlay-chrome.js +56 -0
  352. package/dist/util/overlay.d.ts +71 -0
  353. package/dist/util/overlay.js +240 -0
  354. package/dist/util/photo.d.ts +214 -0
  355. package/dist/util/photo.js +206 -0
  356. package/dist/util/portal.d.ts +22 -0
  357. package/dist/util/portal.js +50 -0
  358. package/dist/util/pull-to-refresh.d.ts +26 -0
  359. package/dist/util/pull-to-refresh.js +30 -0
  360. package/dist/util/radio-keys.d.ts +24 -0
  361. package/dist/util/radio-keys.js +49 -0
  362. package/dist/util/rating.d.ts +22 -0
  363. package/dist/util/rating.js +26 -0
  364. package/dist/util/ready-made-strings.d.ts +76 -0
  365. package/dist/util/ready-made-strings.js +67 -0
  366. package/dist/util/scroll-padding.d.ts +19 -0
  367. package/dist/util/scroll-padding.js +44 -0
  368. package/dist/util/segmented-control.d.ts +15 -0
  369. package/dist/util/segmented-control.js +1 -0
  370. package/dist/util/select.d.ts +40 -0
  371. package/dist/util/select.js +63 -0
  372. package/dist/util/sheet-drag.d.ts +59 -0
  373. package/dist/util/sheet-drag.js +241 -0
  374. package/dist/util/sidebar.d.ts +62 -0
  375. package/dist/util/sidebar.js +38 -0
  376. package/dist/util/sortable-list.d.ts +64 -0
  377. package/dist/util/sortable-list.js +53 -0
  378. package/dist/util/ssr-safe.d.ts +4 -4
  379. package/dist/util/stepper.d.ts +39 -0
  380. package/dist/util/stepper.js +38 -0
  381. package/dist/util/swipeable-list-item.d.ts +35 -0
  382. package/dist/util/swipeable-list-item.js +32 -0
  383. package/dist/util/theme-mode.d.ts +79 -0
  384. package/dist/util/theme-mode.js +121 -0
  385. package/dist/util/toaster.d.ts +92 -0
  386. package/dist/util/toaster.js +75 -0
  387. package/dist/util/tooltip.d.ts +9 -0
  388. package/dist/util/tooltip.js +18 -0
  389. package/dist/util/top-navbar.d.ts +10 -0
  390. package/dist/util/top-navbar.js +5 -0
  391. package/dist/util/touch-target.d.ts +30 -0
  392. package/dist/util/touch-target.js +30 -0
  393. package/dist/util/zabi-strings.d.ts +132 -0
  394. package/dist/util/zabi-strings.js +105 -0
  395. package/dist/zabi-components-colors.css +5 -2
  396. package/dist/zabi-components-theme-dark-only.css +574 -225
  397. package/dist/zabi-components-theme-dark.css +574 -225
  398. package/dist/zabi-components-theme-only.css +1680 -317
  399. package/dist/zabi-components-theme.css +1680 -317
  400. package/dist/zabi-components.css +4473 -1158
  401. package/docs/lucide-icons.md +41 -16
  402. package/docs/theme-imports.md +81 -11
  403. package/package.json +51 -29
  404. package/dist/lib/layout-width-tokens.d.ts +0 -17
  405. package/dist/lib/layout-width-tokens.d.ts.map +0 -1
  406. package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
  407. package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
  408. package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
  409. package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
  410. package/dist/lib/showcase/component-docs/List.d.ts +0 -3
  411. package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
  412. package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
  413. package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
  414. package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
  415. package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
  416. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
  417. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
  418. package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
  419. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
  420. package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
  421. package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
  422. package/dist/lib/showcase/component-docs/index.d.ts +0 -4
  423. package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
  424. package/dist/lib/showcase/components-catalog.d.ts +0 -3
  425. package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
  426. package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
  427. package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
  428. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
  429. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
  430. package/dist/routes/lib/focus-utils.d.ts +0 -12
  431. package/dist/routes/lib/focus-utils.ts +0 -110
  432. package/dist/routes/lib/ssr-safe.ts +0 -58
  433. package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
package/CHANGELOG.md ADDED
@@ -0,0 +1,1924 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## Changelog policy (theme/token surface)
6
+
7
+ Whenever token or CSS import API surface changes, include:
8
+ - exported path additions/removals/deprecations (`package.json` `exports`)
9
+ - token rename/add/remove (for `--color-*` and `--zabi-*`)
10
+ - mapping rule updates (for example dark semantic mapping)
11
+ - migration guidance when compatibility aliases remain temporarily
12
+
13
+ ## [Unreleased]
14
+
15
+ ## [8.1.0] - 2026-10-06
16
+
17
+ 8.1.0 adds a theme generator (`zabi-theme`, `createTheme`) and an accent colour,
18
+ a mobile set (AppShell, AppBar, BottomTabBar, BottomSheet, Drawer, Select as a
19
+ sheet, safe areas, 44px touch targets) and more than 25 new components. Form fields,
20
+ focus rings and light-mode surfaces are easier to see, and `strings` and
21
+ `ZabiStringsProvider` replace every built-in English text. Nothing from 8.0.0
22
+ was removed or renamed: every `exports` path, prop and token is still there.
23
+ 8.0.1 was never published, so everything in the 8.0.1 section below (valid theme
24
+ files, the three-line Tailwind setup working, ColorPicker on the server,
25
+ Progress named) is part of 8.1.0.
26
+
27
+ ### Upgrade notes
28
+
29
+ Coming from 8.0.0, these are visible without a code change. Each bullet says
30
+ what to do.
31
+
32
+ **Appearance**
33
+
34
+ - **Form field edges are darker.** `--color-input-border` is `base-500` in
35
+ light and dark (was `base-350` light, `base-300` dark) and
36
+ `--color-input-border-hover` is `base-550` in light. The edge is guarded at
37
+ 3:1 against the field fill, the page and the card, in both themes. A theme
38
+ of yours that overrides the edge can now report a pair below 3:1 in the
39
+ generator check. The dark field edge on the overlay surface (sheets, modals)
40
+ is about 2:1 against the panel and is not guarded. Nothing to do unless you
41
+ override the edge.
42
+ - **`Progress` uses new tokens.** The fill is `--color-progress-fill` (default
43
+ `--color-action-primary`) and the track `--color-progress-track` (default
44
+ `--color-input`); utilities `bg-progress-fill` and `bg-progress-track`. The
45
+ fill used to be the hard-coded brand step 600, so a bar changes colour in an
46
+ app that pins a primary action, and in dark. Fill on track is guarded at
47
+ 3:1. Set the two tokens to keep the old colour.
48
+ - **Light mode has a visible surface ladder, stronger tints and shadows.** See
49
+ Changed: the page, cards, tinted fills, hover fills, overlay edge and shadow
50
+ all move. A card is 1.18:1 on the page (was 1.10). Check screenshots; no
51
+ token was renamed.
52
+ - **Focus rings are one step darker in light and lighter in dark.**
53
+ `--color-focus` is `brand-600` in both themes (light was `brand-500` at
54
+ 2.99:1 on the page; dark was `brand-500`, 2.91:1 on the overlay; dark is
55
+ #92a9ff by default). The muted ring in dark is one step lighter
56
+ (`--color-focus-ring-muted`, `base-600`).
57
+ - **The computed `box-shadow` of a focused element lists five shadows**, not
58
+ two, because the ring now composes with shadow utilities. A test that reads
59
+ the ring colour from it should pick the 4px spread.
60
+ - **Dark surfaces are `color-mix()` expressions** over `--zabi-base-*`
61
+ (`--color-surface-raised`, `-elevated`, `-overlay`). The pixels are the same;
62
+ `getComputedStyle` reports `color(srgb …)`, not `rgb(…)`, so a test that
63
+ compares the string needs updating.
64
+ - **Placeholder text is one step stronger** in both themes
65
+ (`--color-input-placeholder`: #61616a light, #a1a1aa dark; 4.5:1 or more on
66
+ the resting, hovered, focused and pressed field).
67
+ - **Checked checkboxes and radios use the primary action fill**, with the
68
+ primary button's text colour on the tick and dot.
69
+ - **Dropdown menus, Select lists, the ColorPicker popover and NavigationMenu
70
+ panels have 16px corners** (were 8px), and other nested corners are
71
+ concentric: the Card in a Modal, MediaGrid's check mark, Alert's close
72
+ button, rows in a `.list-group`, the avatar in SidebarFooter's profile
73
+ button.
74
+ - **`font-sans` and `font-mono` follow the theme's font tokens**
75
+ (`--font-family-sans`, `--font-family-mono`), not Tailwind's system stacks.
76
+ If you used `font-sans` to get the system font, write the stack yourself.
77
+ - **`color-scheme` comes from the theme**: `light` by default and for
78
+ `data-theme="light"`, `dark` under `.dark` and `data-theme="dark"` (also when
79
+ both `class="dark"` and `data-theme="light"` are set). Native controls,
80
+ scrollbars, date pickers and autofill follow the theme. A
81
+ `<meta name="color-scheme">` tag is not needed, and a `color-scheme` rule of
82
+ your own must come after the theme import to win.
83
+
84
+ **Markup, roles and accessible names**
85
+
86
+ - **A `Select` is found by role `combobox`, not `button`.** Its trigger has
87
+ `role="combobox"` and reports `aria-invalid`, `aria-required` and
88
+ `aria-busy`. Its form control is a native `<select name>` (not a hidden
89
+ input), so code that looked for `input[type=hidden]` must look for the
90
+ select, and `required` now blocks the form. A form reset returns the Select
91
+ to the value it was rendered with. Update tests and selectors.
92
+ - **A loading `Button` or `IconButton` is `aria-disabled="true"` and
93
+ `aria-busy="true"`, not `disabled`.** It keeps keyboard focus and swallows
94
+ the press. CSS or tests that matched it with `:disabled`, `[disabled]` or a
95
+ strict `toBeDisabled()` must use `[aria-busy="true"]`.
96
+ - **`ThemeToggle` (two modes) has the constant name "Dark mode"** with
97
+ `aria-pressed`, not "Switch to dark mode" / "Switch to light mode"; update
98
+ tests and scripts that find the button by those names. It applies the stored
99
+ choice when it mounts (`storageKey={null}` turns that off) and no longer
100
+ writes an inline `color-scheme`.
101
+ - **`NavigationMenu` is a list of links and disclosure buttons**, not a
102
+ `menubar`: the list has `role="list"` and items carry no menu roles. Screen
103
+ readers announce it differently; keyboard use is unchanged.
104
+ - **A `List` row with no `href` in a List with no `onclick` is plain content**,
105
+ not a focusable button with an arrow.
106
+ - **Checkbox and Radio inputs are stretched invisibly over their box** instead
107
+ of being visually hidden, so `getByRole("checkbox").check()` works.
108
+ - **An unnamed `RadioGroup` submits nothing.** Give it a `name`. (The
109
+ generated name still groups the radios.)
110
+ - **A disabled `Dropdown` or `Select` option is `aria-disabled`, not
111
+ `disabled`**: reachable with the arrow keys, announced as disabled, not
112
+ selectable.
113
+ - A Select list and a Dropdown menu are one Tab stop (roving `tabindex`, 0 or
114
+ -1). `getFocusableElements` skips `tabindex="-1"`.
115
+
116
+ **Defaults and behaviour**
117
+
118
+ - **A binding that starts `undefined` on a bindable prop is now written back
119
+ as the default** (`""`, `false`, `null`). `let value = $state()` works with
120
+ `bind:value` everywhere; no bindable prop has a fallback expression any
121
+ more.
122
+ - **Controls are at least 44px on touch screens** (`pointer: coarse`), so
123
+ layouts there get taller (a checkbox list at an 8px gap is 52px a row).
124
+ Nothing changes with a mouse.
125
+ - **Every pressable control shows a `:active` pressed state** that does not
126
+ depend on hover. Hand-written `:hover` rules apply only under
127
+ `@media (hover: hover)`, so a tapped button no longer sticks in its hover
128
+ colour.
129
+ - **`Page` pads for the safe areas by default** (`safeArea`). An app that sets
130
+ `viewport-fit=cover` and already pads for the insets itself should pass
131
+ `safeArea={false}`. Without `viewport-fit=cover` the insets are zero and
132
+ nothing changes.
133
+ - **`Select` opens as a bottom sheet on a phone** (a touch screen narrower than
134
+ 640px; `presentation="auto"`). Pass `presentation="popover"` to keep the
135
+ pop-over. It fills its container like Input (a Select in a flex row takes the
136
+ free width; give it a width such as `class="w-48"`, which is honoured now),
137
+ and a label too long for the trigger wraps instead of being cut.
138
+ `maxMenuHeight` defaults to `60dvh` (was `60vh`).
139
+ - **`Button` labels wrap.** A label that does not fit goes onto a second line
140
+ and the button grows (`min-height` instead of a fixed height); in a tight
141
+ flex row a button may shrink and wrap where it held its width.
142
+ - **Toasts show your message.** A toast with a `message` and no `title` shows
143
+ the message, not a default heading; only a toast with `detail` can be
144
+ expanded; the countdown sentence and "Click to stop" are off unless
145
+ `showCountdown`. A toast with no `duration` closes after 7 s (14 s when its
146
+ text is 121 to 240 characters, stays when longer); an error toast and a
147
+ toast with an action stay until dismissed. On touch, `onpausechange` reports
148
+ `paused` then `running` for a tap. Toasts also sit above the tab bar, the
149
+ home indicator and the on-screen keyboard, and move while an overlay with a
150
+ header and footer is open. Mount the Toaster inside `ZabiStringsProvider`
151
+ when you use it.
152
+ - **`Tooltip` opens on a tap and can be hovered.** A touch or pen press toggles
153
+ it, and it stays open until a second tap, a tap elsewhere, Escape, a scroll
154
+ or focus leaving (`touchDuration` defaults to `0`). The pointer can move from
155
+ the trigger onto the open bubble, so an open tooltip takes presses: keep
156
+ tooltips clear of other controls. A tooltip that would leave the viewport
157
+ flips or slides to stay 8px inside, and `data-placement` reports the side
158
+ used.
159
+ - **`Tabs` with long labels scroll** instead of wrapping onto several lines.
160
+ - **`AppBar` and `BottomTabBar` stay one row** (titles and labels get an
161
+ ellipsis; the bars are 57px and 65px at any text size). AppBar can be two
162
+ rows tall when the title would have under 72px beside the controls.
163
+ - **Overlays follow the on-screen keyboard** where it covers the page (Modal,
164
+ BottomSheet, SlideUp, Drawer), and Modal and SlideUp are at most `90dvh`.
165
+ - **Light-mode `Input`, `Textarea` and `Select` darken their border on hover**
166
+ and render text at 16px below 640px, so iOS Safari no longer zooms on focus.
167
+ - **`Table` and `PropsTable` sit on the card surface** with an elevated header
168
+ band; Skeleton and Header's variant chips use `bg-neutral-subtle`.
169
+ - **Inside `on-brand` and `on-accent` blocks** labels, links, borders and quiet
170
+ buttons change to the block's on-colour; a card inside a block gets the
171
+ page's colours back. Custom properties named `--zabi-theme-*` appear on a
172
+ block's parent.
173
+ - **`ImageUpload` no longer forces a 16rem minimum width**, and its `id` names
174
+ the control (the dropzone button), not the host. Set a width with `class` if
175
+ you relied on it.
176
+ - **`SidebarAccountPanel`'s `systemMode` default text is "Follows the
177
+ system".**
178
+ - `UnsavedChangesBar` and `ConfirmDialog` leave focus on the pressed button
179
+ while busy, where they moved it to their container.
180
+ - `Modal`'s `onclick` as a close signal is deprecated (see Deprecated).
181
+
182
+ **Theme files and the generator**
183
+
184
+ - **Dark theme files no longer restate the `--zabi-*` ramps.** `theme-dark`,
185
+ `theme-dark-only` and the dark part of `colors` only remap roles; the raw
186
+ palettes are declared once, in the light theme. Import the light theme
187
+ first: `@import "zabi-components/theme-only"; @import
188
+ "zabi-components/theme-dark-only";`. 8.0.0's `.dark`-only file carried a
189
+ copy; a dark file imported on its own now has no ramps. An override of
190
+ `--zabi-brand-*`, `--zabi-accent-*` or `--zabi-base-*` on `:root` applies in
191
+ both modes. The dark files are about 13 KB larger, because the block is
192
+ published for the class and attribute and for the system setting. See
193
+ `docs/theme-imports.md`.
194
+ - **The generator checks more role pairs than 8.0.0**, so a theme check that
195
+ passed on 8.0.0 can turn red. `createTheme` and `zabi-theme` resolve every
196
+ role pair the library's own guard checks (the field edge on fill, page and
197
+ card; the progress fill on its track; the muted and danger focus rings and
198
+ the control boundary on all five surface levels; the accent star outline on
199
+ an inset surface; the focus ring or its offset gap on an accent button; the
200
+ label on a selected and on a held selected pill tab). A pair below AA (4.5:1
201
+ text, 3:1 UI parts) is a warning, and fails under `--strict`. The generated
202
+ values are unchanged; the findings are real for that theme. Fix them with an
203
+ override (`--set`), for example `--color-focus` and `--color-link-hover` one
204
+ brand step darker, or use `--pin`.
205
+ - Theme files work with `[data-theme="dark"]`, and with `[data-theme="auto"]`
206
+ under `prefers-color-scheme: dark`. A page with no class and no attribute is
207
+ light, as before.
208
+
209
+ **Package**
210
+
211
+ - `package.json`: new `./create-theme` export and `zabi-theme` bin; `culori`
212
+ moved to `dependencies` (the generator uses it at run time);
213
+ `sideEffects: ["**/*.css"]`; `CHANGELOG.md` shipped in `files`; the `prepare`
214
+ script renamed `fix:lucide-types` (dev-only, so installing prints no
215
+ install-scripts notice; run it if your editor needs it). Every component
216
+ imports icons from `@lucide/svelte/icons/<name>`; import yours that way too.
217
+ No export, prop, token or file from 8.0.0 was removed or renamed.
218
+
219
+ ### Added
220
+
221
+ #### Theming and the `zabi-theme` generator
222
+
223
+ - **`npx zabi-theme --brand "#0026EA"`** writes a CSS file with the full
224
+ `--zabi-brand-*` ramp; with `--accent` and `--neutral` also the
225
+ `--zabi-accent-*` ramp and all 21 `--zabi-base-*` steps; `--out` names the
226
+ file. Import it after `theme-only` and `theme-dark-only` and it covers light
227
+ and dark. The same is available in code as `createTheme({ brand, accent,
228
+ neutral })` from `zabi-components/create-theme`, which returns `{ css, tokens,
229
+ warnings, closest }`. The ramps sit on the library's lightness curve: your
230
+ colour gives the hue and chroma and is not pinned to a step, so the exact hex
231
+ may not appear, and the file header names the closest step. The generator
232
+ picks the text colour for the brand and accent fills.
233
+ - **The generator reports contrast** on stderr, in the file header and in
234
+ `warnings`; `--strict` exits 1 on a failed pair. `--set <token>=<value>` (or
235
+ `overrides`) moves a role and is checked too. `overrides` takes a flat map
236
+ (both modes) or `{ light, dark, both }`; the CLI has `--set-light` and
237
+ `--set-dark`. A light-only override is restated with the library's own dark
238
+ value in the dark rules, and the check reads each mode's own value.
239
+ - **`zabi-theme --pin`** (`createTheme({ pin: true })`): the primary action is
240
+ the exact brand colour in light, with derived hover and pressed states and a
241
+ label checked at 4.5:1. The focus ring and links take the colour where every
242
+ guarded pair still passes. Dark keeps the mirrored ramp step unless the colour
243
+ passes there; the header says which. A failing pair is a warning and the
244
+ colour is never moved. `--pin-accent` / `pin: { accent: true }` does the same
245
+ for the solid accent fill.
246
+ - **`neutralChroma`** (CLI `--neutral-chroma`, 0 to 0.1, needs `neutral`): the
247
+ OKLCH chroma at the neutral ramp's peak, for a clearly tinted neutral. With it
248
+ the translucent ink roles (`--color-action-secondary` and its hover and
249
+ pressed steps, `--color-surface-hover`, `-active`, `--color-border-overlay`
250
+ in light, `--shadow-color`) follow the neutral ramp, and the file restates the
251
+ dark values under the dark selectors.
252
+ - **A theming guide.** `THEMING.md` is the documented token API: quick start
253
+ with `zabi-theme`, import order, tables of the tokens an app may set (brand,
254
+ accent and neutral ramps, the "on" colours, fonts and weights, radius,
255
+ shadow, z-index) and the roles to read, light, dark and auto, what the
256
+ generator does and does not do, and what does not follow an override.
257
+ Renaming or removing a documented token is a breaking change; the token-name
258
+ snapshot in `npm run test:themes` enforces it. The docs site has a `/theming`
259
+ page with a brand switcher (`?brand=amber` opens any page in Amber, generated
260
+ from `#C17B00` and a warm neutral).
261
+ - **One brand override restyles light and dark.** The raw `--zabi-*` ramps are
262
+ declared once and the dark block only remaps roles; dark surfaces are mixed
263
+ from the neutral ramp, so warm or tinted greys carry through.
264
+ - **An accent colour.** `--zabi-accent-50` … `950`, `--color-accent-50` …
265
+ `950`, the roles `--color-accent`, `-hover`, `-active`, `-subtle`, `-border`,
266
+ `-text`, `--color-on-accent`, and the classes `bg-accent`, `text-accent`,
267
+ `border-accent` (with working `hover:` and `active:` forms, also
268
+ `active:bg-info-active` and `hover:bg-nav-menu-active-hover`). The
269
+ `energetic` tokens are unchanged. `Button` and `IconButton` take
270
+ `variant="accent"` (solid, own hover and pressed fills, `--color-on-accent`
271
+ label); `Badge variant="accent"` (subtle and solid); `Rating tone="accent"`.
272
+ - **The text on a fill can be set.** `--zabi-on-brand` (light, white) and
273
+ `--zabi-on-brand-dark` (dark, `brand-950`) feed `--color-on-brand`, which
274
+ `--color-action-primary-text` follows; `--zabi-on-accent` and
275
+ `--zabi-on-accent-dark` do the same for the accent. `Heading` and `Text` take
276
+ `tone="inherit" | "on-brand" | "on-accent"`; the classes `on-brand` and
277
+ `on-accent` set a block's text colour and make the focus ring visible on it;
278
+ `on-surface` marks a surface of your own inside a block.
279
+ THEMING.md ("Text and controls on a brand or accent block") says which button
280
+ variants stay legible there. Directly on a block an empty or ticked checkbox,
281
+ radio or toggle, a filled Rating star and status message text are not legible
282
+ (put them on a card); a card inside a block needs `:has()`.
283
+ - **Font tokens.** `--font-family-heading` (follows `--font-family-sans` until
284
+ set; applied to `h1`–`h6` and Heading), `--font-family-mono` (CodeBlock reads
285
+ it), `--font-weight-regular`, `-normal`, `-medium`, `-semibold`, `-bold`,
286
+ `--font-sans`, `--font-mono`, `--font-heading`, and a `font-heading` utility.
287
+ - **Dark mode by attribute and by the system setting.** The dark files apply to
288
+ `.dark`, `[data-theme="dark"]`, and `[data-theme="auto"]` when
289
+ `prefers-color-scheme` is dark; `[data-theme="light"]` stays light. Following
290
+ the system is opt-in. Put the class or attribute on `<html>`.
291
+ - **ThemeToggle `modes="three"`** steps through system, light and dark, writes
292
+ `data-theme` on `<html>`, shows a monitor, sun or moon and names itself
293
+ "Theme: system. Switch to light"; `labels` (including `labels.darkMode`)
294
+ replaces the words. `mode` (bindable), `onmodechange` and `storageKey` read or
295
+ drive the mode, or store it under your own key. It reads `dark`, `light` and
296
+ `auto`, follows changes made elsewhere, and shows the right icon before it
297
+ mounts.
298
+ - **Theme helpers** from the package root: `getThemeMode`, `setThemeMode`,
299
+ `isThemeDark`, `getStoredThemeMode`, `storeThemeMode`, `themeInitScript` and
300
+ the `ThemeMode` type. `themeInitScript()` returns a script for `<head>` that
301
+ applies the stored mode before first paint. TopNavbar takes `themeModes`,
302
+ `themeStorageKey` and `themeLabels`; SidebarAccountPanel takes
303
+ `themeModes="three"` with `onThemeModeChange`.
304
+ - New tokens: `--color-surface-inset` (`bg-surface-inset`; a recessed well on a
305
+ card, `base-100` light and `base-150` dark, AA text; dark `--color-input`
306
+ points at it and keeps its value; do not use `--color-input` for a well, it is
307
+ white in light), `--color-input-border-hover`, `--color-control-border`
308
+ (`border-control-border`, `text-control-border`; 3:1 on every surface level;
309
+ `--color-border-strong` is unchanged and decorative), `--color-focus-ring-muted`
310
+ (`.focus-ring--muted`), `--color-focus-ring-danger` (the error colour; the
311
+ on-colour inside a block), `--color-input-active` (`active:bg-input-active`;
312
+ Select's trigger uses it), `--color-action-primary-subtle-active` and
313
+ `--color-action-danger-subtle-active` (`active:bg-…`), `--zabi-list-row-radius`
314
+ (`.list-group` sets it for rows), `--color-progress-fill`,
315
+ `--color-progress-track`, `--zabi-avatar-ring`, and the documented star
316
+ colours `--zabi-rating-on`, `--zabi-rating-off`, `--zabi-rating-on-hover`,
317
+ `--zabi-rating-on-active`, `--zabi-rating-on-edge` (they apply when set on an
318
+ ancestor or by a class). The contrast guard holds the muted and danger focus
319
+ rings and the control boundary to 3:1 on all five surface levels, and fails
320
+ when a focus-ring rule reads a colour that is not in its pair list.
321
+
322
+ #### New components
323
+
324
+ - **SortableList** reorders items and leaves their content to you. Each row
325
+ has a drag handle (mouse and touch), arrow keys, Home and End on the handle,
326
+ optional move up and down buttons and a polite announcement of the new
327
+ position. `bind:items`, `onreorder` (item, old and new index),
328
+ `controls="manual"`, `strings`. Escape cancels a drag; a key that cannot move
329
+ further says so ("Hero section, already first"); a parent replacing `items`
330
+ mid-drag cancels it. Clicks on the handle or a move button do not bubble: put
331
+ them beside a header toggle, never inside another button. Handle and buttons
332
+ sit 8px apart on touch.
333
+ - **Collapsible**: a trigger wired to the panel it shows and hides, owning ids,
334
+ `aria-expanded`, `aria-controls` and the panel's name. `title` gives a
335
+ full-width header button with a chevron (`headingLevel` wraps it in a real
336
+ heading); a `trigger` snippet hands the wiring to your own `<button>`.
337
+ `bind:open`, `onopenchange`, `disabled`. Closed content stays in the DOM under
338
+ `hidden` (`unmountOnClose` removes it). If the panel closes while focus is
339
+ inside it, focus moves to the trigger. **CollapsibleGroup** makes an
340
+ accordion: opening one closes the others, or `multiple`; Arrow Up and Down,
341
+ Home and End move between headers. In a single-open group only the first
342
+ panel marked open renders open (also on the server, without
343
+ `onopenchange`); a disabled panel keeps its state.
344
+ - **ConfirmDialog** (on Modal): `title`, `message`, `variant` (`danger`,
345
+ `warning`, `info`, each with an icon; `danger` uses the danger button),
346
+ `confirmLabel`, `cancelLabel`. If `onconfirm` returns a promise the dialog
347
+ shows its loading state and cannot be dismissed until it settles: it closes on
348
+ success, stays open on failure and passes the error to `onerror`; returning
349
+ `false` keeps it open. `loading` does the same for your own request tracking;
350
+ `oncancel` reports how the user backed out; `loadingLabel` (default
351
+ "Working…") is announced once. Focus starts on Cancel, Enter only activates
352
+ the focused button, and it renders in `document.body` unless `portal={false}`.
353
+ It uses `role="alertdialog"`, draws no outline around the whole dialog while
354
+ loading and sets no `aria-busy` on the panel.
355
+ - **Drawer**: a modal panel from the side. `side` is `left`, `right`, `start` or
356
+ `end`; `size` is `sm`, `md` or `lg`, never wider than the screen; `title`,
357
+ `description`, a `footer` snippet; `dismissible`, `onclose({ reason })`,
358
+ `closeLabel`, `initialFocus`, `portal` (default true). Focus is trapped and
359
+ returns to the opener, the page does not scroll, the slide is skipped under
360
+ `prefers-reduced-motion`, it shares the scroll lock with Modal and SlideUp and
361
+ stacks with them. Its `onkeydown` runs after Escape handling; content with
362
+ nothing to focus is a Tab stop named by the title; the panel pads for the
363
+ bottom safe area; the close button takes taps in a 44px area.
364
+ - **MediaGrid**: image and video thumbnails to pick from (the grid only).
365
+ `getKey`, `getLabel`, `getUrl`; `bind:selected` or, with `multiple`,
366
+ `bind:selectedKeys`. A single selection moves and is never cleared by a repeat
367
+ press. `ondelete` shows a delete button named after each item (focus moves to
368
+ the item that took its place); `getType`, `getPoster`, `loading`, `empty`
369
+ snippet, `emptyHeadingLevel` (default 3), `minTileSize` (96px), `strings`. One
370
+ Tab stop; arrow keys move by the columns on screen, Home and End within the
371
+ row, Delete asks to delete; "Use the arrow keys to move between items." is read
372
+ when focus enters the grid and shown below it while it has keyboard focus. The delete button sits on the end corner, mirrors
373
+ in RTL, takes taps in a 44px area and shows a pressed state on touch.
374
+ - **Spinner**: the loading ring on its own. Sizes `xs` to `lg` (12, 14, 16,
375
+ 20px); with a `label` it is a `status`, without one it is decorative; fades
376
+ instead of spinning under `prefers-reduced-motion`.
377
+ - **Slider**: a native range input in the library's colours. `bind:value`,
378
+ `min`, `max`, `step`, label, helper or error text, three sizes, `showValue`,
379
+ `formatValue` (also `aria-valuetext`).
380
+ - **UnsavedChangesBar**: Save and Discard while a form is `dirty`; sticky
381
+ (`position` `bottom` or `top`); `onsave` may return a promise (saving state;
382
+ a rejection keeps the bar and calls `onerror`); appearance announced politely;
383
+ never takes focus; focus returns to the edited field when it goes; `actions`
384
+ snippet. Warning before the page is left is your app's job.
385
+ - **AppShell**: the phone layout. `header` snippet (AppBar), `<main>` content,
386
+ `footer` snippet (BottomTabBar), a `dvh` column inside the safe areas. Sets
387
+ `--app-shell-top-inset` and `--app-shell-bottom-inset` (fallbacks 57px and
388
+ 65px) on itself and on `<html>` while mounted, for overlays in
389
+ `document.body`. `contentElement="div"` inside a page that already has a
390
+ `<main>`.
391
+ - **AppBar**: a phone top bar with `title` (`headingLevel`, default 1;
392
+ `titleLines={2}` allows two lines), back control (`backHref` or `onback`,
393
+ `backLabel`), `leading` and `actions` snippets (up to two actions),
394
+ `collapseOnScroll` (stays while focus is inside; no animation under reduced
395
+ motion). The title always has at least 72px; a `leading` wider than the row is
396
+ held to the row. Parts carry `data-appbar-part` (`back`, `title`, `actions`);
397
+ the bar measures itself in the browser, so before hydration the title wraps to
398
+ the second row. At 180px wide no title shows beside a back control and two
399
+ actions.
400
+ - **BottomTabBar**: three to five links with an icon above a short label.
401
+ `items` take `href`, `label`, `icon`, `badge` (read with the label: "Inbox, 3
402
+ new"; `badgeLabel`, `badgeMax`). `active` is an href or path (`aria-current=
403
+ "page"`); left out, the bar follows the browser address, so pass
404
+ `active={page.url.pathname}` on the server. Fixed on its own and in the flow
405
+ in an AppShell (`position`), clear of the home indicator. Tabs are at least
406
+ 44px with 8px gaps; under 52px wide the labels hide (icon only, label stays
407
+ the accessible name); tabs go under 44px only when five do not fit. The
408
+ active tab has a 2px outline in the action colour. With five tabs labels stay
409
+ 11 to 12px at 200% text.
410
+ - **Rating**: one to `max` (default 5) stars, can be empty. `bind:value` is a
411
+ number or `null`; a radio group named by `label`, each star reads "4 of 5
412
+ stars"; arrow keys, Home and End; `name` submits it. A repeat press on the
413
+ selected star never clears it; `clearable` adds a clear button (Delete and
414
+ Backspace clear too). `readonly` shows a score as one image ("Quiz, 3.5 of 5
415
+ stars"), with fractions, `showValue`, `formatValue`; it submits nothing. 44px
416
+ targets at every `size`; `strings`; the empty star uses
417
+ `--color-control-border`.
418
+ - **SegmentedControl**: two to four choices in one row. `options` (`value`,
419
+ `label`, `icon`, `disabled`), `bind:value`, `onchange`. A radio group, not
420
+ tabs: one Tab stop, arrows move and select, `name` submits, a repeat press
421
+ does nothing. `fullWidth` (default true), labels wrap, 44px tall on touch.
422
+ - **BottomSheet**: a modal panel sliding up from the bottom. Snap points
423
+ (`snapPoints`, `"half"` and `"full"` by default; `bind:snap`); the grip drags
424
+ between them or down to close and is a button ("Expand" / "Collapse"). Content
425
+ scroll wins over dragging unless at its top. `title`, `description`, `footer`,
426
+ `isOpen`, `dismissible`, `portal`, `initialFocus`, `closeLabel`,
427
+ `onclose({ reason })` with the extra reason `"swipe"`. Safe areas, no animation
428
+ under reduced motion, a centred 40rem sheet from `md`. Half and full heights
429
+ are shares of what the on-screen keyboard leaves. The grip's focus ring is
430
+ drawn inside its 44px box; a click reaches the grip button, and flicks are
431
+ timed from the events' own times.
432
+ - **StickyActionBar**: keeps a form's main action in view at the bottom of the
433
+ screen or scrolling box, rises above the on-screen keyboard, reserves
434
+ `scroll-padding-bottom`, never takes focus. One per scrolling box; a short
435
+ form needs `class="flex min-h-full flex-col"`.
436
+ - **FloatingActionButton**: a round 56px primary button. `label` (required, the
437
+ accessible name), `extended`, `icon`, `href` (a link; a button otherwise),
438
+ `position` (`bottom-end` default, `bottom-start`, `bottom-center`). Above the
439
+ tab bar in an AppShell, above the safe area elsewhere; `--fab-bottom-offset`
440
+ lifts it; it reserves `scroll-padding-bottom` on what scrolls under it.
441
+ - **DateField** and **TimeField**: the browser's date and time inputs in the
442
+ library's Input. `bind:value` is `YYYY-MM-DD` or 24-hour `HH:mm`, `""` when
443
+ empty; `label`, `hint`, `error`, `min`, `max`, `step`, `required`, `disabled`,
444
+ `readonly`, `size`, `name`; work in FormField; ids start with `input-`.
445
+ **`formatDate` and `formatTime`** (package root) format those strings for a
446
+ locale (`formatDate("2026-10-06", "sv")` is "6 okt. 2026"), never shift by the
447
+ time zone, and return `""` for empty or invalid.
448
+ - **Calendar**: one month as an ARIA grid with event dots. `bind:month`
449
+ (`YYYY-MM`), `bind:selected` (`YYYY-MM-DD` or `null`), `events` (`date`,
450
+ `label`, `tone`; up to three dots a day), `weekStartsOn` (default Monday),
451
+ `locale`, `min`, `max`, `isDateDisabled`, `onselect`, `onmonthchange`,
452
+ `strings`. One Tab stop: arrows by day and week, Home and End within the
453
+ week, Page Up and Down by month (Shift: year). Days are named in full with
454
+ state and events and are 44px tall; unavailable days have a line through the
455
+ number. `locale` translates dates only, so pass `strings` for "today",
456
+ "selected", "unavailable". Pressing the selected day does nothing.
457
+ - **DropdownItem**: the menu item as a component for a Dropdown's custom
458
+ `children`; takes its role from the Dropdown and joins the arrow-key order. A
459
+ DropdownItem with custom children and a description is named by its content
460
+ alone.
461
+ - **Stepper**: progress through a multi-step form. `steps` (labels or `{ label,
462
+ description }`), `bind:current` (zero-based), `layout` (auto goes compact below
463
+ 30rem of its own width: it suits about five short labels; compact segments are
464
+ narrower than 44px from seven steps with `interactive` on 320px), `size`
465
+ (`sm`, `md`, `lg`), `strings`. Completed steps show a check, the current one
466
+ its number, upcoming an outline; a navigation landmark with
467
+ `aria-current="step"`; a step change is announced once. `interactive` makes
468
+ completed steps buttons. It does not move focus: focus the new heading
469
+ yourself. In Safari 16 and older it needs a width from its parent in a flex
470
+ row.
471
+ - **PhotoGrid**: square thumbnails that open a viewer. `photos` (`src`,
472
+ `thumbSrc`, `alt`, `width`, `height`, `caption`, `id`), `columns` (3, then 4
473
+ and 5), `max` ("+N" tile), `onopen(index)`, `onadd` ("Add photo" tile),
474
+ `selectable` (`"single"` or `"multiple"`, `bind:selected`,
475
+ `bind:selectedKeys`; a single selection is never cleared by a repeat press and
476
+ each tile gets an Open button). One Tab stop, lazy loading, placeholder and
477
+ failure fallback. **PhotoViewer**: a full-screen dialog for them, `bind:index`,
478
+ `bind:isOpen`, caption, counter, up to three `actions` (more in a menu).
479
+ Swipe or arrows change photo; pinch, double tap, Ctrl+wheel or `+`/`-` zoom;
480
+ drag pans; swipe down, Escape or the close button close it
481
+ (`onclose({ reason })`). Focus returns to the tile; the thumbnail shows
482
+ blurred until the full image loads; neighbours are preloaded; dark in both
483
+ themes; safe areas. Double tap is timed from the touch events. Previous and
484
+ Next at the ends keep their plate and dim only the arrow.
485
+ - **Avatar** and **AvatarGroup**: `Avatar` is a round picture falling back to
486
+ initials (`name`, `src`, `size` `sm` 24, `md` 32, `lg` 48px, `alt`, `alt=""`
487
+ for decorative, `locale`); initials come from the first and last word, are in
488
+ the server markup and remain if the picture fails. They are `brand-200` on
489
+ `brand-700` in the default light theme (the subtle primary pair, 4.5:1).
490
+ `AvatarGroup` shows `people` overlapped as a list, up to `max` (default 4) and
491
+ "+3" (`strings.more`), `locale`. Not interactive.
492
+ - **SwipeableListItem**: a row with one or two `actions` revealed by a swipe
493
+ towards the inline start (touch and stylus; mirrored in RTL); a swipe only
494
+ reveals, an action runs on press. The same actions are behind a "more"
495
+ button (`showMoreButton`, on by default; with `false` offer another route).
496
+ Bindable `open`, `onopenchange`, `strings.actions`; one row open per list.
497
+ - **PullToRefresh**: wraps a list; pulling down at the top calls `onrefresh`
498
+ (touch only). Bindable `refreshing`, `disabled`, `threshold` (64px),
499
+ `strings`. A Refresh button shown on keyboard focus does the same; a status
500
+ region says "Refreshing" then "Updated". A rejected `onrefresh` ends the busy
501
+ state silently: report the failure in the app. In controlled mode it says
502
+ "Updated" whenever `refreshing` goes back to false.
503
+ - **`focusToasts()`** moves focus to the newest toast; focus returns when it is
504
+ dismissed, also from inside an open overlay.
505
+
506
+ #### Existing components
507
+
508
+ - **ImageUpload**: `onbrowse` replaces the native chooser and
509
+ `event.preventDefault()` in `onclick` keeps it closed. A video value (file
510
+ type, extension, or `previewType`) renders a `<video>` with controls, never
511
+ autoplaying; a `preview` snippet replaces the preview. `label` and `id` (the
512
+ dropzone is a native `<button>`); Change and Remove are named from the label
513
+ ("Change logo") or `changeLabel` and `removeLabel`; the preview takes `alt`.
514
+ A dropped file is checked against `accept` and rejected through
515
+ `onfilereject`. Copy props: `browseText`, `changeText`, `removeText`,
516
+ `errorTitle`, `errorRecovery` (`false` leaves the line out), `selectedText`,
517
+ `removedText` ("Image selected" / "Image removed" are announced);
518
+ `actionsPlacement` (`"overlay"` or `"strip"`, strip by default for video).
519
+ - **IconButton**: `xs` size (24px, the minimum target; use `sm` or larger where
520
+ touch is primary); `pressed` (bindable, `aria-pressed` and a pressed style in
521
+ every variant; `event.preventDefault()` in `onclick` keeps the state; left
522
+ undefined it renders no `aria-pressed`); `tone="danger"` on `ghost` or
523
+ `outline` for a quiet destructive style. `href` on Button and IconButton
524
+ renders a real link (`target`, `rel`, `download`); a disabled or loading link
525
+ has no `href` and is `aria-disabled`.
526
+ - **Modal**: `portal` (renders in `document.body`; a theme class set below
527
+ `<body>` does not reach it); `onclose({ reason: "escape" | "backdrop" |
528
+ "close-button" })`; `dismissible={false}` (the close button stays focusable,
529
+ `aria-disabled`; setting `isOpen` still closes); `role="alertdialog"`;
530
+ `closeLabel` (default "Close"); `fullScreen` (or `"mobile"` below `md`);
531
+ `initialFocus` (also on SlideUp); typed `aria-describedby`, `aria-busy`,
532
+ `data-*`, `id`. SlideUp: `closeLabel`, `swipeToClose` (grip, downward swipe,
533
+ calls `onclick`), a `footer` snippet kept above the keyboard.
534
+ - **Toast and Toaster**: `pushToast` takes `action: { label, onclick,
535
+ dismissOnClick? }` (a button in the toast, announced with the message, closes
536
+ after the handler unless `dismissOnClick` is `false`; the countdown pauses
537
+ while pointer or focus is on it; give it a long `duration` or `0`; the
538
+ `ToastAction` type is exported); `duration` takes `"short"` (3 s), `"medium"`
539
+ (7 s), `"long"` (14 s), `"persistent"` or milliseconds, with `defaultDuration`
540
+ on Toaster (`TOAST_DURATIONS`, `ToastDuration` exported); `strings` for every
541
+ built-in word (`ToasterStrings`, `DEFAULT_TOASTER_STRINGS`), `aria-label`,
542
+ `showCountdown`, `onpausechange({ id, paused })`, `data-paused`; `closeLabel`
543
+ on Toast and Alert; `--toaster-bottom-offset` (72px clears a
544
+ FloatingActionButton); other attributes pass to the region. A held finger
545
+ pauses a toast, lifting resumes with at least 3 s left. Toasts do not fly
546
+ under reduced motion; one with an action stays until dismissed.
547
+ - **Dropdown**: options take `icon`, `tone: "danger"` and `description` (the
548
+ `DropdownOption` type is exported). `presentation` (`"auto"`, `"popover"`,
549
+ `"sheet"`; default `"popover"`): in a sheet the same menu opens in a
550
+ BottomSheet titled by the control's label; `<Dropdown presentation="sheet">`
551
+ is the library's action sheet (`sheetTitle`, `sheetSnap`, `sheetCloseLabel`,
552
+ `sheetExpandLabel`, `sheetCollapseLabel`, `fullWidth`, optional
553
+ `ariaLabelledby`). Menu boxes report `data-resolved-placement`.
554
+ - **Select**: renders a real `<select>` on the server, the working control
555
+ before hydration and with scripts off; on mount the custom list takes over,
556
+ and a choice made before is kept and reported once; `presentation="native"`
557
+ renders only the native select. `presentation` (`"auto"`, `"popover"`,
558
+ `"sheet"`, `"native"`), `strings` (including `listLabel`;
559
+ `DEFAULT_SELECT_STRINGS`, `SelectStrings`, `SelectPresentation` exported,
560
+ also from atoms; the six text props still win). The chosen option has a check
561
+ mark at the inline end and a fill, and every option carries `aria-selected`.
562
+ A disabled Select submits its value once hydrated and not before; the inline
563
+ message of a `required` Select is the browser's, in the browser's language; a
564
+ label longer than one line is cut in the native select and wraps in the custom
565
+ one, so the field can grow by a line.
566
+ - **Input, Textarea, Select**: `hint` and `error` (a hint is tied with
567
+ `aria-describedby`, an error sets the error state and is announced;
568
+ `variant` + `message` still work and your own `aria-describedby` is merged).
569
+ Input takes `leading` and `trailing` snippets and, for `type="password"`,
570
+ `revealable` (`revealLabel`, default "Show password", `aria-pressed`). Input,
571
+ Textarea, Select, Checkbox, Radio, Toggle and ThemeToggle accept their
572
+ element's native attributes (`autocomplete`, `inputmode`, `maxlength`,
573
+ `enterkeyhint`, `data-*`); Table and Text pass other attributes on. Toggle
574
+ takes `aria-label` and `aria-labelledby` ("Toggle" is only the last fallback).
575
+ `Checkbox` is exported from the atoms entry.
576
+ - **Table**: `stacked` (or `"sm"`, `"md"`, `"lg"`) lays rows out as label and
577
+ value pairs and drops the 20rem minimum; put `data-label` on each cell and
578
+ keep the `<thead>`; `captionHidden` keeps the caption as the accessible name
579
+ but hides it. A stacked cell keeps several children together on the value
580
+ side; a cell without `data-label` puts its content there too; an empty cell
581
+ leaves no blank line.
582
+ - **EmptyState**: `headingLevel` (1–6, default 2); `size="compact"` (tighter,
583
+ smaller title, a plain `<div>` not a named region; the default is still a
584
+ `<section>` named by its title).
585
+ - **TopNavbar**: nav items take `external` (default true for absolute URLs);
586
+ `collapseAt` (`"sm"`, `"md"` default, `"lg"`, `"xl"`; `TopNavbarCollapseAt`
587
+ exported).
588
+ - **Tabs**: `fullWidth` (for two or three tabs; scrolls when a share is too
589
+ small for the longest word). The rule under Tabs is `border-border`; a
590
+ selected pill uses `action-primary-subtle`.
591
+ - **SidebarShell**: `mobile="drawer"` hides the sidebar below `lg` and opens it
592
+ in a Drawer from the start side: `bind:isOpen`, `trigger` snippet
593
+ (`aria-expanded` wiring), `drawerTitle`, `closeLabel`, `onclose({ reason })`;
594
+ it closes when a link is followed and when the screen widens past 1024px;
595
+ `label` names the scrolling region (default "Navigation links").
596
+ SidebarNavigation declares and passes these on. Default `mobile="none"`
597
+ changes nothing.
598
+ - **Page**: `safeArea` (default true) pads left, right and bottom by
599
+ `env(safe-area-inset-*)`; nothing inside an AppShell or where the insets are
600
+ zero; your own `px-*` class replaces that side. Needs `viewport-fit=cover`.
601
+ - **Tooltip**: `touchDuration` (default `0`); one open at a time; a tooltip on a
602
+ disabled button is tied to it with `aria-describedby`. Do not put essential
603
+ information in a tooltip.
604
+ - **ColorPicker**: the colour map is two native sliders (saturation, lightness)
605
+ in one Tab stop, working by keyboard (Shift moves ten steps), touch and pen;
606
+ new strings `area`, `saturation`, `lightness`, `invalidHex`; the hue slider
607
+ shows its focus ring and is 44px on a coarse pointer.
608
+ - **CodeBlock**: `copyLabel`, `copiedLabel`; "copied" is announced through a
609
+ status region. **FormField**: `requiredLabel`.
610
+ #### Mobile and touch
611
+
612
+ - New props for phones: `Page` `safeArea`, `Modal` `fullScreen`, `SlideUp`
613
+ `swipeToClose` and `footer`, `TopNavbar` `collapseAt`, `Tabs` `fullWidth`,
614
+ `Tooltip` tap, `Select` and `Dropdown` `presentation`, `SidebarShell`
615
+ `mobile="drawer"`, `Toaster` `--toaster-bottom-offset`. Components for them:
616
+ AppShell, AppBar, BottomTabBar, BottomSheet, Drawer, StickyActionBar,
617
+ FloatingActionButton, SwipeableListItem, PullToRefresh, PhotoViewer. Safe areas
618
+ need `viewport-fit=cover`.
619
+
620
+ #### Accessibility
621
+
622
+ - A focus ring in forced colours: `.focus-ring`, the legacy `.focus-brand` and
623
+ `.focus-nav`, and the checkbox and radio row draw an outline there. The
624
+ current page has an outline in forced colours in SidebarNavigation,
625
+ SidebarPanel and TopNavbar. In forced colours every option of a Select list
626
+ has the same border and the chosen one is told apart by its check mark only.
627
+ - `docs/ACCESSIBILITY.md` has a "Library conventions" section (where focus goes
628
+ when a focused control is removed, the shared overlay stack and scroll lock,
629
+ disabled options, hover-revealed actions, focus in forced colours) and no
630
+ longer lists fixed issues as open; `docs/KEYBOARD_NAVIGATION.md` describes
631
+ what the components do.
632
+
633
+ #### Strings and hydration
634
+
635
+ - **`ZabiStringsProvider`**: wrap the app in `<ZabiStringsProvider
636
+ strings={…}>` and every component under it uses those words unless an
637
+ instance says otherwise. One entry per component with a `strings` object
638
+ (including `avatarGroup`, `swipeableListItem`, `pullToRefresh`), keyed by prop
639
+ name for components with single-text props (`toast`, `alert`, `codeBlock`,
640
+ `imageUpload`, `unsavedChangesBar`), and a `common` group (close, back, expand,
641
+ collapse, required, showPassword, search, confirm, cancel). Order, later wins:
642
+ built-in English, provider, the instance's `strings`, its own text props. It
643
+ is Svelte context (per request on the server, nestable, reaches portalled
644
+ overlays); Modal, Drawer, SlideUp, BottomSheet, Toaster, Rating and the
645
+ components above read it; landmark `ariaLabel`s are not covered.
646
+ `getZabiStrings()`, `DEFAULT_ZABI_COMMON_STRINGS` and the types are exported.
647
+ No translations ship; the README has a complete Swedish example. Nothing
648
+ changes outside a provider.
649
+ - **Every built-in text can be replaced**: `strings` on TopNavbar,
650
+ SidebarFooter, SidebarAccountPanel, SidebarNavigation, SidebarBrandHeader,
651
+ ColorPicker, ContactForm, PropsTable, ComponentDemo, Select, Toaster, Stepper,
652
+ Calendar, MediaGrid, SortableList, Rating, PullToRefresh. The README's "Texts
653
+ and other languages" lists the prop for each component.
654
+ - **A choice made before the page hydrates is kept.** A Checkbox, Radio,
655
+ RadioGroup, Rating, SegmentedControl or Select changed in the server's markup
656
+ was set back at hydration; the bound value now follows the native input and
657
+ the change callback fires once (a form reset still returns to the rendered
658
+ value). A bound Select value with no matching option is kept and submitted as
659
+ itself.
660
+ - **No component drops keyboard focus on hydration** (Heading, Text,
661
+ CardHeader, AppBar's title, EmptyState's heading, Container, AppShell,
662
+ Collapsible); a guard fails the build if a dynamic element comes back.
663
+
664
+ #### Package and build
665
+
666
+ - Exports and package fields are listed under Upgrade notes. `zabi-components/types`
667
+ matches the components again (see Fixed). `npm run build:css` works in a fresh
668
+ checkout with no `dist/`.
669
+ - The docs site honours `data-theme`, and Storybook's toolbar Theme control
670
+ drives its sidebar, toolbar and docs pages. `docs/theme-imports.md` says what
671
+ to expect when Tailwind and `zabi-components/css` are combined (the compiled
672
+ stylesheet carries its own copy of each utility, so import order decides: with
673
+ Tailwind in the app import `theme-only` and `theme-dark-only` instead).
674
+ `THEME.md` no longer tells apps to import the compiled stylesheet after
675
+ `theme-only` or to use `theme()` for a colour. `THEME_QUICK_REFERENCE.md`
676
+ shows the current primary colour and dark mirror; `docs/VARIANTS.md`, the
677
+ README, the Storybook introduction and the showcase guide cover the new
678
+ components. The component docs site examples are written in English. The
679
+ site's catalog sidebar is a drawer below 1024px and its "On this page" list
680
+ marks the section being read; the site sets `viewport-fit=cover`.
681
+
682
+ ### Changed
683
+
684
+ - **Light mode has a visible surface ladder.** `--color-surface-base`,
685
+ `--color-page` and `--color-background-tertiary` move from `base-100` to
686
+ `base-150`; `--color-surface-elevated` from `base-50` to `base-100`; a card is
687
+ 1.18:1 on the page (was 1.10) and a nested card 1.10:1 (was 1.04).
688
+ `--color-card-active` and `--color-surface-overlay-hover` are `base-150`.
689
+ - **Light tinted fills read on a white card.** `--color-<family>-subtle` moves
690
+ from step 100 to 200 and `--color-<family>-border` from 200 to 300 for
691
+ success, warning, error, energetic and info (1.36:1 on a card, was 1.16);
692
+ `--color-neutral-subtle` is `base-250`, `--color-neutral-border` `base-300`;
693
+ `--color-action-primary-subtle` and `-subtle-hover` are `brand-200` and
694
+ `brand-300`.
695
+ - **Light hover and secondary fills are stronger.** `--color-surface-hover` and
696
+ `--color-surface-active` are 9% and 15% ink (were 6% and 11%);
697
+ `--color-action-secondary`, `-hover`, `-active` are 10%, 15%, 20% (were 7%,
698
+ 12%, 17%).
699
+ - **Overlays have an edge in light** (`--color-border-overlay` is a 10% ink tint,
700
+ was transparent); **light shadows are stronger** (`--shadow-color` `24 24 27`,
701
+ `--shadow-opacity` 0.14; were `0 0 0` and 0.1).
702
+ - **Light fields and disabled controls follow the new page**:
703
+ `--color-input-hover` `base-100`, `--color-input-disabled` the page colour,
704
+ `--color-action-disabled` `base-250`, `--color-action-disabled-border`
705
+ `base-300`.
706
+ - **Focus tokens**: `--color-focus` is `brand-600` in both themes;
707
+ `--color-focus-weak`, `-medium`, `-strong` each move one step;
708
+ `--color-nav-menu-focus` follows `--color-focus`; `.focus-ring--muted` (ghost
709
+ and link buttons, the AppBar back control, the SortableList handle) reads
710
+ `--color-focus-ring-muted`, 3.7:1 or more on every surface level (dark was
711
+ `base-500`, 2.49:1 on elevated, 1.98:1 on overlay). Dark mode otherwise keeps
712
+ its values: `.dark` restates the values it used to inherit.
713
+ - **Dark mapping**: `--color-action-primary-text` follows `--color-on-brand`;
714
+ dark `--color-base-*` steps alias the mirrored `--zabi-base-*` step; the dark
715
+ block holds no raw palette and no hex value.
716
+ - **Menus share one edge and surface**: Dropdown, NavigationMenuContent and the
717
+ ColorPicker popover use `border-border-overlay` on `bg-surface-overlay` (the
718
+ ColorPicker popover no longer sits on the inset field colour in dark). Dropdown
719
+ menus and Select lists use the 16px overlay radius, and Select's search field
720
+ and options sit 4px closer to the list's edge. ColorPicker's popover and
721
+ NavigationMenu's panels use the overlay radius.
722
+ - **Nested corners are concentric** where a rounded element sits closer to its
723
+ container's corner than that corner's radius: the Card in a Modal, the avatar
724
+ in SidebarFooter's profile button, MediaGrid's check mark (a rounded square)
725
+ and video badge, Alert's close button, ListItem rows in a `.list-group`.
726
+ - Input, Textarea and Select text is 16px below 640px (also ColorPicker's hex
727
+ field and the search fields in Select and the sidebars); sizes unchanged from
728
+ `sm` up and control heights unchanged.
729
+ - **Checked checkboxes and radios use the primary action fill**, tick and dot in
730
+ the primary button's text colour (was a paler step at 2.8:1 on the light page).
731
+ - Input, Select and Textarea darken their border on hover in light
732
+ (`--color-input-border-hover`); error, success, warning and disabled fields
733
+ are unchanged. Select's trigger placeholder uses the placeholder colour.
734
+ - Table and PropsTable sit on the card surface with an elevated header band;
735
+ Skeleton and Header's variant chips use `bg-neutral-subtle`.
736
+ - Sidebar search fields, the selected panel item, the elevated panel and the
737
+ profile button use full-strength rings instead of 40–80% alpha ones; dashed
738
+ empty states keep only their dashed border. SidebarNavigation's search
739
+ placeholder keeps 4.5:1 on hover.
740
+ - **Controls are 44px on touch** (`@media (pointer: coarse)`): Button, IconButton,
741
+ Input and the Select trigger at `sm` and `md` (`lg` is 48px already; use it
742
+ for the main action on a phone), and Slider rows, Checkbox, Radio and Toggle
743
+ rows, Tabs, Collapsible triggers, Dropdown items, Select options (48px),
744
+ NavigationMenu, TopNavbar and Sidebar items, SortableList controls and toast
745
+ buttons. The close buttons of Alert, Toast, Modal, SlideUp and Drawer and the
746
+ Toggle switch keep their size and take taps in a 44px area. IconButton `xs`
747
+ stays 24px.
748
+ - **Every pressable control shows a `:active` state**; a disabled Toggle or Tab
749
+ no longer shows one. A toggled-on ghost, outline, link or danger-tone
750
+ IconButton shows a pressed fill distinct from hover and 1.25:1 or more from
751
+ rest. The selected pill Tab's pressed fill is 1.42:1 from rest (was 1.24:1)
752
+ and its label keeps 4.89:1.
753
+ - SlideUp pads its content for the bottom safe area and is at most `90dvh`
754
+ (was `90vh`); Modal is at most `90dvh`.
755
+ - **Tabs scroll sideways when they do not fit**, with a fade on the edge that has
756
+ more, and bring the selected and keyboard-focused tab into view; labels no
757
+ longer wrap or squeeze.
758
+ - **Toasts**: sit above the tab bar and home indicator (the stack clears
759
+ `--app-shell-bottom-inset` and the bottom safe area; below `sm` it spans the
760
+ width with 16px gutters; a `viewport` Toast at the top clears the top safe
761
+ area and an AppShell header); keep clear of an overlay's footer (16px above
762
+ it, or at the top when there is no room; a centred desktop Modal changes
763
+ nothing); take the larger free stretch above the panel or between header and
764
+ footer, scrolling there, without covering an overlay's title and close button;
765
+ sit above the on-screen keyboard; `--toaster-bottom-offset` is not added on top
766
+ of an overlay's footer. Tab moves from an overlay's last control to the toast
767
+ controls and back, and Escape in a toast returns focus without closing the
768
+ overlay. Padding, gaps and button targets are in px and the status icon stays
769
+ 20px at enlarged text (a two-sentence toast at 375px and 200% text is at most
770
+ 420px tall, was 594px); buttons wrap under the title when it would get less
771
+ than 8rem; the stack is capped to the screen and scrolls. The countdown says
772
+ "1 second", not "1 seconds".
773
+ - **Tooltip**: flips or slides to stay 8px inside the viewport, also with a
774
+ mouse; below 640px a `left` or `right` tooltip opens above or below; a tap
775
+ opens it when the finger comes up where it went down, so a scroll that starts
776
+ on the trigger no longer flashes it; `data-placement` reports the side used.
777
+ - **Overlays stay above the on-screen keyboard** where it covers the page; the
778
+ focused field is scrolled into view.
779
+ - **Overlay headers stay compact at large text sizes.** In BottomSheet, Modal,
780
+ Drawer and SlideUp the header's padding, gaps, grip and close button are in px
781
+ and the title grows to 1.3 times and stops; at 200% text a half-height sheet
782
+ gives the content 378 of 576px (was 182). Titles break at a hyphen in the
783
+ page's language and a too-long word no longer sticks out of Drawer or SlideUp.
784
+ - **Select fills its container** and no longer changes width with the chosen
785
+ option; its trigger is a `combobox` reporting `aria-invalid` (with `error`, the
786
+ error variant or a failed `required` check), `aria-required` and `aria-busy`;
787
+ `required` blocks an empty form, shows the browser's message as the error and
788
+ focuses the trigger; a long label wraps; the form control is a `<select
789
+ name>`; a reset returns it to its rendered value; it opens as a sheet on a
790
+ phone; the chevron and the Dropdown panel do not animate under reduced
791
+ motion; keyboard focus in the native select moves to the trigger at handover;
792
+ `aria-label` and `aria-labelledby` name the native select too; with several
793
+ empty required Selects only the form's first invalid control takes focus.
794
+ - **A loading Button or IconButton** is `aria-disabled` and `aria-busy` (see
795
+ Upgrade notes); a loading link keeps its place in the Tab order. Button labels
796
+ wrap (`min-height`: 32, 40, 48px; 44px minimum on touch).
797
+ - **A disabled Dropdown option stays focusable** (`aria-disabled`).
798
+ - **A toast with an action stays until dismissed** unless given a `duration`;
799
+ with no duration a toast closes after 7 s (see Upgrade notes); a message with
800
+ no title is the toast's text.
801
+ - **ThemeToggle** (two modes) has the name "Dark mode" and applies the stored
802
+ choice on mount in both modes; it no longer writes an inline `color-scheme`
803
+ and removes a stale one, so adding or removing `class="dark"` from your own
804
+ script moves native controls with the tokens.
805
+ - **EmptyState at `size="compact"`** is a plain `<div>`.
806
+ - **ImageUpload** fills its container; `id` names the control; Change and Remove
807
+ appear on `:focus-visible` (not any focus), wrap and truncate in a narrow
808
+ container, stay visible on coarse pointers and where hover is unavailable, and
809
+ sit on an opaque plate so "Change" is readable over any image (it was 2.59:1 in
810
+ dark over a light image).
811
+ - **`font-sans` and `font-mono` follow the theme.**
812
+ - **A List row with nothing to do is plain content**; a disabled plain ListItem
813
+ row no longer carries `aria-disabled`.
814
+ - Checkbox and Radio inputs are stretched invisibly over their box.
815
+ - Textarea and Select status messages use the same text colour step as Input's.
816
+ - **Icons are imported one file at a time** from `@lucide/svelte/icons/<name>`,
817
+ not the `@lucide/svelte` barrel: an app importing one Button from the root
818
+ compiles 366 modules where it compiled 3,800 (233 from
819
+ `zabi-components/atoms`). The re-exported icons keep their names.
820
+ - The `viewport` prop on NavigationMenu only sets `isMobile` on the context for
821
+ your own children; it has no effect of its own, and the docs now say so.
822
+ - **`Toast`/`Alert`/`CodeBlock`/`ImageUpload`/`UnsavedChangesBar`** follow
823
+ `ZabiStringsProvider`.
824
+
825
+ ### Deprecated
826
+
827
+ - **Modal's `onclick` is deprecated as a close signal.** It is still called with
828
+ the event on Escape, a backdrop click and the close button; use `onclose`.
829
+ - In `zabi-components/types`, members that were exported but never existed
830
+ (`open`, `className`, `position`, Button's `ariaLabel` and icon props) stay as
831
+ optional and deprecated so existing code keeps compiling.
832
+
833
+ ### Fixed
834
+
835
+ - **`strings` props and `ZabiStringsProvider`:** a key set to `undefined` keeps
836
+ the English default instead of blanking the text (`null` and an empty string
837
+ are still honoured).
838
+ - **`Progress`:** a passed `id` is on the wrapper only (it appeared on the
839
+ wrapper and the bar); `aria-valuenow` is clamped to 0..`max`; a non-positive
840
+ or non-finite `max` falls back to 100; new `aria-label` and `aria-labelledby`
841
+ props name the progressbar.
842
+ - **`ColorPicker`:** Escape closes the popover and returns focus to the swatch
843
+ (and does not close a Modal around it); opening by keyboard moves focus in;
844
+ typing a hex redraws the colour map; with a `label`, the hex input and swatch
845
+ are named by it. Its popover could be placed below the viewport at 320px; a
846
+ press on a toast no longer closes it.
847
+ - **`Select`:** the popover listbox is named by the field's label (still "Select
848
+ options" with no label); **`Dropdown`** gets an optional `ariaLabelledby`
849
+ prop. Arrow keys get past a disabled option. The Select trigger's pressed
850
+ state shows in light (was the resting fill) and is 1.27:1 against rest in
851
+ light and 1.31:1 in dark (was 1.10:1). A Select no longer changes its value
852
+ when the page hydrates. A disabled Select dims its value. Dropdown and Select
853
+ return focus to the trigger when the menu closes from inside it; focus your
854
+ own handler moved elsewhere is left alone. A Select list and a Dropdown menu
855
+ are one Tab stop: Shift+Tab goes to the search field in one step, and a
856
+ character typed on an option goes to the search field or the matching item.
857
+ A press on a toast no longer closes an open Select or Dropdown.
858
+ - **`SwipeableListItem`:** a long action label wraps (it was clipped at the start
859
+ at 200% text), the action area is capped at two thirds of the row, and the
860
+ "more" button stays 44px at any text size. With `showMoreButton={false}` focus
861
+ has nowhere to return to after an action.
862
+ - A long Modal title no longer squeezes the close button or runs out of the
863
+ panel (the title wraps, also inside a single long word; the close button keeps
864
+ 32px).
865
+ - A Tooltip with `strategy="fixed"` no longer covers its trigger when it opens
866
+ above or to the left; a closed tooltip no longer widens the page; it has an
867
+ edge in forced colours, is centred correctly in RTL and its arrow points at its
868
+ trigger in RTL; only one tooltip is open at a time and the pointer can cross a
869
+ diagonal to its far corner. Escape with a tooltip showing dismisses the
870
+ tooltip only, not the modal or sheet it sits in.
871
+ - A Dropdown stays on screen: measured before paint, it flips when the preferred
872
+ side does not fit, slides back when neither does and is capped to the viewport
873
+ with its own scroll only when it must be (8px margin). `placement` is still
874
+ the preferred side; a menu that fits is positioned as before. A Dropdown, Select
875
+ list or NavigationMenu panel is no longer cut off by a scrolling or clipping
876
+ ancestor such as a Modal's content (the nearest clipping ancestor decides the
877
+ side; when it fits on neither it is positioned against the viewport; an
878
+ ancestor with a transform or filter cannot be escaped), and a menu leaves a
879
+ clipping container only when that puts it on screen. A height-capped Dropdown
880
+ scrolls inside its rounded panel. Dropdown follows the writing direction
881
+ (`bottom-start` and `top-start` sit on the start edge, item text aligns to it).
882
+ - NavigationMenu fits a narrow screen: the list wraps onto further rows and a
883
+ content panel slides back inside the viewport. A press on a toast no longer
884
+ closes a NavigationMenu panel or the TopNavbar menu.
885
+ - TopNavbar's phone menu closes after a link in it is followed, when
886
+ `currentPath` changes, on Escape (focus returns to the menu button), on a click
887
+ or focus move outside the bar and when the screen widens past the breakpoint;
888
+ a press on a control behind the open menu still reaches it. It is at most as
889
+ tall as the screen below the bar and scrolls, allows for the top safe area,
890
+ its links fill the row, and `aria-controls` on the menu button is set only
891
+ while the menu exists. The menu button stays on screen with a long brand or
892
+ enlarged text (the brand truncates below the breakpoint).
893
+ - Toasts fit a 320px screen (the 18rem minimum width no longer overflows); the
894
+ close button takes taps in its full 44px area; `aria-controls` on the expand
895
+ button is set only while the detail is open; focus no longer falls to the page
896
+ after "Okay" or "Click to stop" is pressed; dismissing the focused toast moves
897
+ focus to the next toast or back to where it was; a tap neither sticks nor
898
+ dismisses; toasts do not fly in or out under `prefers-reduced-motion`.
899
+ - A disabled Checkbox, Radio or RadioGroup row no longer dips, changes fill or
900
+ shows the pressed fill when pressed or hovered; stacked checkbox and radio
901
+ rows no longer overlap on touch; the row shows its hover
902
+ (`--color-surface-hover`, `--color-surface-active`, was a fixed `base-100`).
903
+ Checkbox shows its loading ring (replacing the tick, fading under reduced
904
+ motion).
905
+ - Alert's close button shows the library's focus ring and mirrors in RTL.
906
+ - State variants beside a semantic colour class work: the hand-written colour
907
+ classes sit outside every cascade layer, so `text-description
908
+ hover:text-headline` never changed on hover. Thirteen such variants the
909
+ components use are restated by hand (the hover colour of close buttons, the
910
+ hover border of outline buttons, the disabled text colour of fields and ghost
911
+ buttons).
912
+ - Accent hover and pressed states work: `hover:bg-accent-hover` and
913
+ `active:bg-accent-active` did nothing next to `bg-accent`.
914
+ - An overlay opened later is always on top: Modal and SlideUp take a z-index
915
+ that grows with the number of open overlays, so a modal opened from a portalled
916
+ one no longer opens behind it; a modal opened while a portalled drawer is open
917
+ (and the reverse) is drawn on top.
918
+ - Modal and SlideUp keep Tab inside, and still close on Escape, when focus has
919
+ fallen out of the dialog because the focused control was disabled or removed.
920
+ A modal or sheet with nothing focusable takes focus itself. Modal, Drawer,
921
+ SlideUp and BottomSheet no longer take focus from content that focused itself
922
+ inside the panel as it opened. Focus returns to a replaced element (the one
923
+ that now has the opener's id) instead of `<body>`. Modal and SlideUp do not
924
+ slide in under `prefers-reduced-motion`.
925
+ - The focus ring survives a shadow: `.focus-ring` on an element with a `shadow-*`
926
+ or `ring-*` utility (also `shadow-none`) drew no ring (the SidebarNavigation
927
+ search field, the selected SidebarPanel row, an interactive Card, any
928
+ IconButton given a shadow); it now composes. A List no longer clips the focus
929
+ ring of its rows.
930
+ - Placeholder contrast: placeholder text and the format hint of an empty
931
+ DateField or TimeField was 3.40:1 on a dark field and 4.40:1 on a hovered light
932
+ one; see Upgrade notes. A disabled field's placeholder is no stronger than a
933
+ disabled value.
934
+ - Native controls follow the dark theme under `.dark` as well as `data-theme`
935
+ (scrollbars, date and time pickers, other native form controls, autofill); a
936
+ page that sets `color-scheme` on `<html>` keeps its value.
937
+ - Hover colours no longer stick after a tap: the hand-written `:hover` rules in
938
+ the theme files (`.bg-action-primary:hover` and 41 more) now sit in `@media
939
+ (hover: hover)`.
940
+ - ImageUpload keeps keyboard focus (to Change after a selection, to the dropzone
941
+ after Remove, untouched when focus is elsewhere), cancels a file dropped while
942
+ `disabled`, its actions layer no longer takes clicks meant for the preview, its
943
+ error message is tied to the Change button when a preview is showing, and a
944
+ disabled dropzone no longer takes the primary border on hover.
945
+ - ActionPanel shows its hover and pressed states (the fill is drawn over the
946
+ card). A selected Tabs pill shows its pressed state and a selected List row its
947
+ selected border. ListItem rows show a hover and a pressed fill.
948
+ SidebarFooter's avatar ring used a colour token that does not exist and now uses
949
+ the border colour. A `dark:` variant in Section's accent background, which
950
+ followed the OS setting and never applied, was removed.
951
+ - ThemeToggle shows the right icon before it mounts (both icons are in the
952
+ markup and the `dark` class picks one); it works with `data-theme`.
953
+ - ConfirmDialog no longer draws a focus outline around the whole dialog while
954
+ loading and sets no `aria-busy` on the panel.
955
+ - A Badge with a long label wraps: it is at least 20, 24 or 28px tall, grows when
956
+ the label wraps and is never wider than its container; a one-line badge is
957
+ unchanged to the pixel and a wrapped one reads as a rounded rectangle.
958
+ - An AppBar's title is never squeezed out (see AppBar).
959
+ - Keyboard focus is visible in forced colours (see Accessibility).
960
+ - **`zabi-components/types` matches the components again.** Every exported
961
+ `*Props` interface declares the props its component accepts today: `ModalProps`
962
+ has `isOpen`, the interfaces have `class`, and the new props are there.
963
+ `SelectProps.options`, `AlertProps.message` and `TooltipProps.content` are
964
+ optional, as on the components.
965
+ - A bindable prop bound to `undefined` no longer stops the page from hydrating
966
+ (RadioGroup threw, and 36 other bindable props could); see Upgrade notes.
967
+ - Button and IconButton (including `variant="accent"`) do not scale when pressed
968
+ under `prefers-reduced-motion`; built-in loading rings (Button, IconButton,
969
+ Input, Textarea, ActionPanel, Toggle) fade instead of spinning, Checkbox's
970
+ ring stops; Toggle's loading ring has its gap back.
971
+ - Input, Textarea and Select no longer point `aria-describedby` at a message that
972
+ is not rendered.
973
+ - `fullWidth` Tabs scroll instead of breaking a word; BottomSheet's grip answers
974
+ a mouse click; a flick on a sheet is measured from the events' own times.
975
+ - Drawer content clears the home indicator; MediaGrid's remove button shows a
976
+ pressed state on touch; SidebarAccountPanel's three-mode theme row follows a
977
+ theme changed elsewhere; CodeBlock announces "copied".
978
+ - The brand menu in the docs site header stays on screen (it opened past the
979
+ right edge at 768px and 1024px); the Pine and Citron accents follow the Iris
980
+ recipe in light (fill at step 600 with a white label, focus ring at step 600;
981
+ Pine's light ring was 1.93:1 on the page, now 4.19:1); `theme-color` follows
982
+ the theme toggle; the site's cards carry `shadow-sm` and the showcase uses
983
+ surface tokens in place of raw ramp steps.
984
+ - README links to RELEASING.md point at the repository, and the README says to
985
+ install `@lucide/svelte` and import icons per file.
986
+
987
+ ## [8.0.1] - 2026-09-29
988
+
989
+ Fixes the documented setup. In 8.0.0 (and 7.0.2) the three-line Tailwind setup
990
+ from the README left components unstyled; `@import "zabi-components/css"` was
991
+ the only import that worked. No tokens or `exports` paths were added, removed
992
+ or renamed.
993
+
994
+ ### Fixed
995
+
996
+ - **Theme files are valid CSS again.** `theme`, `theme-only`, `theme-dark` and
997
+ `theme-dark-only` were written without semicolons between declarations, so a
998
+ CSS parser read all 355 tokens as one declaration. `scripts/build-css.js` now
999
+ terminates each declaration.
1000
+ - **`theme` and `theme-only` carry an `@source` directive** for the package's
1001
+ own components. Tailwind CSS v4 does not scan `node_modules`, so the classes
1002
+ the components use were never generated. No `@source` line is needed in your
1003
+ own CSS.
1004
+ - **`theme` and `theme-only` ship the hand-written component rules**:
1005
+ `.focus-ring` and its variants, `.text-action-primary`, the action hover,
1006
+ active and disabled states, the semantic colour classes and the `z-*` scale.
1007
+ These existed only in the compiled `css` bundle. Without them a primary
1008
+ button's label took the colour of its fill, and nothing had a focus ring.
1009
+ - **ColorPicker renders on the server.** Its teardown ran in `onDestroy`, which
1010
+ also runs during SSR, and threw `window is not defined`. It is now returned
1011
+ from `onMount`.
1012
+ - **Progress has an accessible name.** Its label was a `<label for>`, which
1013
+ only names form controls, so screen readers announced a progressbar with no
1014
+ name. The label is now referenced through `aria-labelledby`.
1015
+
1016
+ ### Changed
1017
+
1018
+ - The universal-selector scrollbar rules (`* { scrollbar-width: thin }` and the
1019
+ `*::-webkit-scrollbar` family) are in the compiled `css` bundle only, not in
1020
+ the theme files, so importing a theme does not restyle every scrollbar in
1021
+ your app. `.scrollbar-semantic` is in both and is the opt-in.
1022
+ - `theme` and `theme-only` grew from about 22 kB to about 35 kB.
1023
+ - `scripts/validate-theme.js` parses the theme files instead of pattern-matching
1024
+ them, and fails the build if the token block does not parse or the component
1025
+ rules are missing. The old check passed on the broken files.
1026
+
1027
+ ### Migration
1028
+
1029
+ Nothing to change if you import `zabi-components/css`. If you worked around the
1030
+ broken theme files with your own `@source` line or your own copies of
1031
+ `.focus-ring` or `.text-action-primary`, you can remove them.
1032
+
1033
+ ## [8.0.0] - 2026-09-22
1034
+
1035
+ Consolidates two development cycles. 7.1.0 and 7.2.0 were both prepared but
1036
+ never published, so the last release on npm is 7.0.2 and everything below ships
1037
+ together in 8.0.0.
1038
+
1039
+ ### Breaking changes since 7.0.2
1040
+
1041
+ What can break a working 7.0.2 consumer. Each item is detailed in the sections
1042
+ that follow.
1043
+
1044
+ - **The `zabi-components/react` subpath is gone** from `exports`. It pointed at
1045
+ `dist/react`, which was never built, so importing from it already failed at
1046
+ resolution — but the subpath no longer exists at all.
1047
+ - **Three tokens were removed**: `--color-action-primary-disabled`,
1048
+ `--color-action-secondary-disabled`, `--color-action-danger-disabled`. Use the
1049
+ shared `--color-action-disabled`, `--color-action-disabled-text` and
1050
+ `--color-action-disabled-border` instead. Your own CSS referencing the old
1051
+ names will silently resolve to nothing, so grep for them before upgrading.
1052
+ - **Every chromatic ramp was regenerated** against a shared lightness curve, and
1053
+ the semantic families moved from step 500 to step 600. Rendered colour changes
1054
+ throughout, in both themes, with no opt-out. Screenshot tests will all churn.
1055
+ - **`max-w-*`, `w-*` and `min-w-*` with `xs`–`2xl` resolve to container sizes
1056
+ again** (`max-w-lg` is 32rem, not 1.5rem). This is a bug fix, but anyone who
1057
+ compensated for the broken values in their own CSS must undo that.
1058
+ - **No install scripts run in consumer installs.** The `postinstall` hook is
1059
+ gone; it is now a dev-only `prepare` script and is not in the tarball.
1060
+
1061
+ Not breaking, but worth knowing: the published tarball no longer contains
1062
+ `dist/lib/**`. Those were declaration files for this repo's demo site and
1063
+ showcase, emitted by mistake; no `exports` entry ever pointed at them.
1064
+
1065
+ ### Design system
1066
+
1067
+ Outcome of a full design review of all 63 components. The headline change is
1068
+ that the colour ramps are now generated against one shared lightness curve, so
1069
+ the semantic families read as a single family instead of six unrelated colours.
1070
+
1071
+ #### Colour — BREAKING (visual)
1072
+
1073
+ - **All chromatic ramps regenerated** (`brand`, `citron`, `pine`, `iris`,
1074
+ `warning`, `error`) against a shared CIE L\* curve defined in
1075
+ `tokens/chromatic-scales.js`. `<ramp>-600` now means the same lightness in
1076
+ every ramp. Previously the ramps spanned L\* 34.6–75.0 at step 500 — a
1077
+ 40-point spread at the same nominal step — and `pine` fell 29.8 points between
1078
+ 400 and 500, which made half that ramp unusable.
1079
+ - **Semantic tokens moved to step 600** across the board. `warning`, `error` and
1080
+ `energetic` previously sat at step 500 (the dark mirror's fixed point) and so
1081
+ did not invert between themes at all, while `success` and `info` did. Solid
1082
+ badges were 2.15:1 (warning) and 1.97:1 (energetic) in light mode.
1083
+ - **New per-family tokens**: `--color-<family>-subtle` (step 100),
1084
+ `--color-<family>-border` (step 200). `--color-<family>-text` is now step 700
1085
+ for every family.
1086
+ - **`--color-action-primary` is now `brand-600`** (was `brand-950`). The primary
1087
+ action carries the brand colour in both themes.
1088
+ - **`--color-action-primary-active` no longer jumps across the ramp.** It was
1089
+ `brand-400` against a `brand-50` label — 1.66:1, so the label vanished while
1090
+ the button was held. Hover/active now step darker in light, lighter in dark.
1091
+ - **`--color-action-secondary`** is a neutral alpha tint instead of a 10% brand
1092
+ wash, which was too pale to read as a control.
1093
+ - **New**: `--color-action-disabled`, `--color-action-disabled-text`,
1094
+ `--color-action-disabled-border` — one quiet neutral pair shared by every
1095
+ variant. Disabled was previously `brand-500` + `opacity-50`, which made a
1096
+ disabled primary *lighter and more colourful* than an enabled secondary.
1097
+ - **New**: `--color-surface-hover`, `--color-surface-active` — surface-relative
1098
+ alpha tints. `hover:bg-base-100` resolved to exactly `--color-surface-base` in
1099
+ dark mode, so ghost buttons had no hover feedback on the app shell.
1100
+ - **New**: `--color-control-track{,-hover,-active}` for switch rails.
1101
+ - **Removed**: `--color-action-{primary,secondary,danger}-disabled` (use
1102
+ `--color-action-disabled`); the raw-hex `.dark` danger overrides (danger now
1103
+ mirrors through `--color-error-*` like every other family).
1104
+ - `--color-input` is now a raised surface (white in light, an inset step in
1105
+ dark) rather than a grey well that read as disabled.
1106
+ - `--color-input-placeholder` is wired up — it was defined but unused.
1107
+ - `--color-link` moved to `brand-700`: `brand-600` cleared AA on a card but only
1108
+ managed 4.42:1 on the page surface.
1109
+
1110
+ #### Geometry — BREAKING (visual)
1111
+
1112
+ - **One control height scale**: `sm` 32px, `md` 40px, `lg` 48px, shared by
1113
+ Button, IconButton, Input, Select and Textarea. They previously rendered at
1114
+ 40/48/64, 32/40/48 and 38/46/50 — a `lg` Button stood 14px taller than the
1115
+ `lg` Input beside it.
1116
+ - **Four role-based radii** replace eight t-shirt radii: `--radius-control`
1117
+ (8px), `--radius-container` (12px), `--radius-overlay` (16px),
1118
+ `--radius-pill`. Radius no longer changes with size. `--radius-sm/md/lg/xl`
1119
+ remain as legacy aliases for consumer overrides.
1120
+ - Button type is monotonic again: weight is `font-medium` at every size (it used
1121
+ to drop to `font-normal` at `lg`, so the biggest button had the lightest
1122
+ label), and the arbitrary letter-spacing values are gone.
1123
+ - **Elevation is two steps**, not four. `shadow-sm` means raised (cards, tables,
1124
+ sidebars, toggle knobs), `shadow-lg` means floating (modals, sheets, menus,
1125
+ toasts); `shadow-none` stays the explicit absence of one. `shadow-md` and
1126
+ `shadow-xl` are gone, and so is the bare `shadow` on the Toggle knob, which
1127
+ resolved to a Tailwind default `app.css` never defined. An elevated Card no
1128
+ longer deepens its shadow on hover — it was already at the floating step, so
1129
+ the hover now changes background only.
1130
+ - **Layout spacing snaps to the 4px grid.** 30 half-step utilities
1131
+ (`gap-1.5`, `gap-2.5`, `px-2.5`, `px-3.5`, `py-2.5`, `space-y-1.5`, …) were
1132
+ setting a second rhythm against the first across forms, sidebars and badges.
1133
+ Margins are deliberately exempt: `mt-0.5` beside a first line of text is an
1134
+ optical alignment nudge, not rhythm, and Alert, ListItem, ToasterToast and the
1135
+ radio control still use it.
1136
+ - A one-row `Textarea` is now 40px like the `Input` beside it (was 44px, from
1137
+ `py-2.5`).
1138
+
1139
+ #### Components
1140
+
1141
+ - **`Badge`**: rebuilt. New `emphasis` prop (`subtle` | `solid`, default
1142
+ `subtle`), plus `class`, `children` and `...restProps` — none of which existed,
1143
+ despite stories shipping for two of them. Pill radius instead of 2px. Renders
1144
+ nothing when it has no label, rather than leaving an empty box.
1145
+ - **`Alert`**: tinted fill + tinted border instead of a bare 1px outline on a
1146
+ transparent ground; full-width by default with a new `inline` prop; one Lucide
1147
+ icon family instead of hand-rolled SVGs of mixed weight.
1148
+ - **`Heading`**: no longer hardcodes `font-family: "Nunito Sans"` in a local
1149
+ `<style>` block — it uses `--font-family-sans`, so rebranding the font token
1150
+ now actually rebrands headings. `level` is typed `1 | 2 | 3 | 4 | 5 | 6`
1151
+ instead of `number`; new `size` prop decouples visual size from semantic
1152
+ level; `children` snippet supported; display sizes carry negative tracking.
1153
+ - **New `SidebarShell`**: the chrome of a sidebar with the regions left open —
1154
+ width, surface, collapse behaviour, the shared inset and a scrolling middle.
1155
+ Regions are snippets (`header` / `children` / `footer`), each handed
1156
+ `{ collapsed, insetX }`. `SidebarNavigation` is 39 props on one component and
1157
+ is excellent right up until your sidebar is not shaped like that one, at
1158
+ which point none of the parts are reachable; compose `SidebarBrandHeader`,
1159
+ `SidebarNavSection` and `SidebarFooter` into the shell instead and skip the
1160
+ ~30 props you never set. `SidebarNavigation` is now built on it rather than
1161
+ duplicating the chrome, so the two cannot drift. Additive — its prop API is
1162
+ unchanged.
1163
+ - **`SidebarNavigation`**: its Props table documented 7 of 39 props. All 39 are
1164
+ documented now, with types and defaults.
1165
+ - **`Text`**: now sits on the same type ramp as `Heading` rather than carrying
1166
+ its own — `size="md"` matches an `h6` and `size="lg"` an `h5`, line box
1167
+ included, and a new `xs` step (12px) extends the bottom. Leading is stated
1168
+ rather than inherited from Tailwind's defaults, so the shared steps stay
1169
+ locked together if the scale is retuned. **New `weight` prop** (`normal` |
1170
+ `medium` | `semibold` | `bold`) lets body copy carry emphasis without being
1171
+ promoted to a heading; `tone="label"` defaults to `medium`, every other tone
1172
+ to `normal`. Purely additive — existing `sm`/`md`/`lg` call sites are
1173
+ unchanged visually.
1174
+ - **`Input`**: raised surface, quieter placeholder, `min-w-48` removed.
1175
+ - **`Card`**: radius no longer scales with size; `outlined` uses `border-border`.
1176
+
1177
+ #### API consistency
1178
+
1179
+ - **Class overrides actually win now.** `class` being "merged last" only held
1180
+ for properties the component didn't already set: `rounded-control` and
1181
+ `rounded-container` are equal-specificity utilities, so the winner was
1182
+ whichever Tailwind emitted later. The sidebar's search field asked for
1183
+ `rounded-container` and rendered at 8px. Every component now merges through
1184
+ `cn()` (tailwind-merge, a new runtime dependency), with the role-based radii
1185
+ declared explicitly since `control`/`container`/`overlay`/`pill` are role
1186
+ names rather than scale values that tailwind-merge could recognise.
1187
+ - **`class` is now the public prop on every component.** 25 components exposed
1188
+ `className`, 15 exposed `class`, and 21 exposed neither. `className` is kept
1189
+ everywhere as a deprecated alias and both are merged, so existing call sites
1190
+ keep working.
1191
+ - Every component spreads `...restProps` onto its host element.
1192
+ - `Button`: `fullWidth` replaces `isFullWidth` (deprecated alias retained).
1193
+
1194
+ #### Guardrails
1195
+
1196
+ - **New** `scripts/check-ramp-lightness.js` — fails if a ramp drifts off the
1197
+ shared curve (±1.5 L\*) or develops a cliff (>15 L\* between adjacent steps).
1198
+ - **New** `scripts/check-contrast.js` — resolves every fill/foreground pair a
1199
+ component can render, in both themes, and fails below WCAG AA. 84 pairs.
1200
+ - **New** `scripts/check-control-geometry.js` — fails if controls disagree on
1201
+ height, or if any component uses a t-shirt radius.
1202
+ - **New** `scripts/generate-ramps.js` + `tokens/chromatic-scales.js` — ramp
1203
+ generation, wired into `npm run sync:tokens`.
1204
+ - `scripts/check-token-violations.js` now also rejects fixed ramp steps in
1205
+ interaction states (`hover:bg-base-*`, `active:bg-base-*`), which is how the
1206
+ invisible ghost hover got in.
1207
+ - **New** `scripts/generate-surfaces.js` + `tokens/surface-ladder.js` — the four
1208
+ dark surface levels are now derived from one wash ladder instead of four
1209
+ independently tuned hex values, and regenerate with `npm run sync:tokens`.
1210
+ `--color-surface-elevated` moves `#35353a` → `#363638` and
1211
+ `--color-surface-overlay` `#44444c` → `#454547` (slightly less blue); base and
1212
+ raised were already exactly on the ladder.
1213
+ - The ramp-interaction rule now also scans `src/lib/marketing` and the landing
1214
+ route. The rest of the checks guard what ships in the package, but
1215
+ `hover:bg-base-100` had reached two marketing specimens, where dark mode
1216
+ painted the hover darker than the card it sat on.
1217
+ - `scripts/check-token-violations.js` also rejects **off-scale shadows**
1218
+ (anything but `shadow-sm` / `shadow-lg` / `shadow-none`, including a bare
1219
+ `shadow`) and **half-step spacing** on gap/space/padding utilities. Margins
1220
+ are exempt by design. Comment lines are skipped so prose about shadow DOM
1221
+ doesn't trip it.
1222
+ - New scripts: `npm run check:ramps`, `check:contrast`, `check:geometry`, and
1223
+ `check:design` (all four). `build:css` and `check` run `check:design`.
1224
+
1225
+ #### Stories
1226
+
1227
+ - `Badge` → `AllVariantsWithIcons` no longer crashes. It used an invalid CSF
1228
+ render shape (`Component: 'div'` with a `children` array) cast through
1229
+ `as any`, so TypeScript could not catch it and it threw at runtime. Replaced
1230
+ with real harness components, which also fixes `WithChildren` and
1231
+ `WithCustomClass` (both silently rendered nothing).
1232
+
1233
+ ### Forms
1234
+
1235
+ - **`ContactForm`**: invalid submissions no longer fall through to a native POST and page reload.
1236
+ - **`ImageUpload`**: `onchange` / `onclick` are now actually called; `value` supports `bind:value`; new `onfileselect({ file, url })` exposes the selected `File`. Change/Remove actions are visible on keyboard focus, and Space activates the drop zone.
1237
+ - **`ColorPicker`**: 3-digit hex (`#f00`) is parsed correctly; `value` supports `bind:value`.
1238
+ - **`Form`**: accepts standard form attributes and a `novalidate` prop; docs note that `onsubmit` handlers must call `preventDefault()` for client-side handling.
1239
+
1240
+ ### Widgets
1241
+
1242
+ - **`Tabs`**: Arrow/Home/End move focus as well as selection; the tablist is no longer an extra tab stop; `activeTab` supports `bind:activeTab`.
1243
+ - **`Select`**: new `name` prop renders a hidden input for native form submission; the search field sits outside the `listbox`; string/number option values compare consistently.
1244
+ - **`Dropdown`**: new optional `header` snippet; Home/End/Space behave normally inside text inputs in the menu.
1245
+ - **`Toggle`**: new `id`, `name` and `value` props for label pairing and form submission.
1246
+ - **`ThemeToggle`**: a consumer `onclick` no longer overrides the toggle; pre-mount placeholder respects `size`/`variant` (no layout shift).
1247
+
1248
+ ### Overlays
1249
+
1250
+ - **`Modal` / `SlideUp`**: `role="dialog"` moved from the backdrop to the panel; body scroll locked while open (nested-safe); focus trap picks up content added while open; Escape closes only the topmost dialog. `Modal` gains `showClose` (default `true`) so untitled modals still have a close button.
1251
+ - **`Toaster`**: countdown is no longer announced every second; it pauses on hover/focus; no double announcements from nested live regions.
1252
+ - **`NavigationMenu`**: fixed a leaked document `mousedown` listener; panels use the disclosure pattern (no `role="menu"` / `aria-haspopup`); new `ariaLabel` prop.
1253
+ - **`Dropdown`**: arrow keys, Home and End now reach `menuitemradio` and `menuitemcheckbox` items. The item query matched only `menuitem` and `option`, so a menu built from radio items had nothing to focus: opening it by keyboard focused nothing and Tab closed it, leaving the items unreachable.
1254
+ - **`Alert`**: `closable` now hides the alert; new bindable `open` prop.
1255
+ - **`Tooltip`**: Escape no longer steals focus; CSS custom properties no longer leak onto `:root`.
1256
+ - **`util/ssr-safe`**: timer helpers typed with `ReturnType<typeof setTimeout/setInterval>` instead of `NodeJS.Timeout`, so consumers don't need `@types/node`.
1257
+
1258
+ ### Surfaces
1259
+
1260
+ - **New semantic surface elevation tokens** (both themes), lowest → highest: `--color-surface-base` (page), `--color-surface-raised` (cards, panels, sidebars), `--color-surface-elevated` (nested cards, hover/active fills), `--color-surface-overlay` (modals, sheets, menus, toasts), plus `--color-surface-overlay-hover` (row/icon hover inside overlays) and `--color-border-overlay` (1px overlay edge: transparent in light, visible in dark). Utilities: `bg-surface-base`, `bg-surface-raised`, `bg-surface-elevated`, `bg-surface-overlay`, `bg-surface-overlay-hover`, `border-border-overlay`.
1261
+ - **Dark mode elevation no longer relies on shadows**: levels step up +6 OKLCH lightness each (L 21 → 27 → 33 → 39) on the neutral base hue. Light mode is visually unchanged.
1262
+ - **Existing tokens are now aliases** (no renames): `background` → `surface-base`; `card` → `surface-raised`; `surface-1` → `surface-raised`; `surface-2` → `surface-elevated`; `card-elevated` → `surface-overlay` (light) / `surface-elevated` (dark); in dark, `card-hover` → `surface-elevated`, `card-active` and `surface-3` → `surface-overlay`. Dark values of `card`, `card-hover`, `card-active`, `surface-1`–`surface-3` shift slightly to land on the new levels.
1263
+ - **Floating components use the overlay level**: `Modal`, `SlideUp`, `Dropdown` (and `Select`'s menu), `NavigationMenu` panels, `Toaster` toasts and the `Toast` atom now paint `bg-surface-overlay` (previously `bg-card` / `bg-surface-1`, which in dark matched or undercut the card they floated over). Hover fills inside them use `bg-surface-overlay-hover` so they read above the overlay. `Modal`/`SlideUp` get a `border-border-overlay` edge. `Card variant="elevated"` uses `bg-card-elevated`.
1264
+ - **Dark `--color-description`** moves one step lighter (`base-650`) so description text keeps ≥ 4.5:1 contrast on every surface level, including overlays.
1265
+ - **New guard**: `scripts/check-surface-elevation.js` (run by `validate-theme.js` during `build:css`) fails the build if dark levels aren't strictly increasing, any step is outside 5–8 OKLCH L points, overlay hover/tooltip fills are darker than the overlay, or a floating component paints a surface below overlay.
1266
+
1267
+ ### Theme fixes
1268
+
1269
+ - **`max-w-*`, `w-*` and `min-w-*` with `xs`–`2xl` now use Tailwind's container sizes again** (`max-w-lg` = 32rem). Tailwind v4 resolves these utilities through `--spacing-*` before `--container-*`, so the theme's named spacing tokens made `max-w-lg` resolve to 1.5rem for anyone importing the theme. New `--max-width-*`, `--width-*` and `--min-width-*` tokens (xs 20rem … 2xl 42rem) are checked first. Spacing utilities such as `p-lg` / `gap-md` are unchanged.
1270
+ - **`--color-caption` meets WCAG AA on every surface level**: light `base-500` → `base-550` (was 4.40:1 on the page background, now ≥ 5.58:1); dark `base-500` → `base-650` (was 2.00:1 on overlays, now ≥ 5.00:1). To keep caption visibly softer than description in dark mode, dark `--color-description` moves from `base-650` to `base-700` (≥ 6.53:1).
1271
+ - **New guard**: `scripts/check-surface-elevation.js` also fails the build if `headline`, `body`, `label`, `description` or `caption` drop below 4.5:1 on any surface level in either theme.
1272
+ - **`Modal`**: the title and close button line up with the body and footer. The inner `Card`'s own padding stacked on the header's padding and indented the title 24px past the content.
1273
+
1274
+ ### Packaging
1275
+
1276
+ - **Removed `zabi-components/react`**: the export pointed at `dist/react`, which was never built, and the wrappers were out of sync with the Svelte components. The optional `react` peer dependency and `@types/react` are gone too.
1277
+ - **No more `postinstall` in consumer installs**: `scripts/fix-lucide-svelte-icon-dts.js` only ever patched this repo's own `node_modules` (from a consumer install it resolved to a non-existent nested path and did nothing). It now runs as a dev-only `prepare` script and is no longer shipped in the tarball. Installs with `--ignore-scripts` (pnpm/Bun defaults) are unaffected.
1278
+ - **`zabi-components/lib/ssr-safe` and `zabi-components/lib/variant-utils` now resolve to compiled `.js` + `.d.ts`** (`dist/util/ssr-safe.*`, `dist/routes/lib/variant-utils.*`) instead of raw `.ts` sources. Import paths are unchanged.
1279
+ - **`generateId` / `createId` from the package root** now use the shared `util/ssr-safe` implementation; the previous root version used `Date.now()` on the server, so two ids created in the same millisecond collided.
1280
+ - **`CodeBlock`**: window-chrome dots use semantic tokens (`bg-error` / `bg-warning` / `bg-success`) instead of raw Tailwind palette classes; props are now typed.
1281
+ - **`check:tokens`** also flags raw Tailwind palette utilities (e.g. `bg-red-500`) in `src/components`.
1282
+ - **Repo**: `dist/`, `.svelte-kit/` and `storybook-static/` are no longer tracked in git; unused Storybook template assets and `src/_tmp-lucide-import.ts` removed.
1283
+ - **CI**: Node 24; the smoke workflow now also runs `check`, unit tests, theme tests and the token scan; publish runs `check` and unit tests before building.
1284
+
1285
+ ## [7.0.2] - 2026-04-20
1286
+
1287
+ ### Changed
1288
+
1289
+ - **`Tooltip`**: Tooltip surface styling now uses theme tokens **`--color-tooltip-bg`** / **`--color-tooltip-fg`** (with **`bg-tooltip-bg`** / **`text-tooltip-fg`** utilities) and **`z-tooltip`**, keeping layout/arrow logic in scoped CSS while aligning colors with the design system.
1290
+ - **`Tooltip` layout**: Tooltip panel uses **`width: max-content`** with a responsive **`max-width`** cap (`min(24rem, calc(100vw - 2rem))`) so typical copy stays on one line until the limit; arrow fill follows **`--color-tooltip-bg`**.
1291
+ - **Theme (`src/app.css`)**: Defines **`--color-tooltip-bg`** / **`--color-tooltip-fg`** (defaults to base-800 / base-50); **`prefers-contrast: high`** maps them to **`CanvasText`** / **`Canvas`** for stronger contrast. Regenerated **`zabi-components*.css`** picks up the new utilities.
1292
+
1293
+ ## [7.0.1] - 2026-04-19
1294
+
1295
+ ### Fixed
1296
+
1297
+ - **`postinstall`**: The Lucide typings patch script is **included in the published package** (`scripts/fix-lucide-svelte-icon-dts.js` via `package.json` `files`), so `npm install zabi-components` no longer fails when lifecycle scripts run.
1298
+ - **Theme CSS (Tailwind v4)**: Replaced `theme(colors.*)` inside `@theme` / `.dark` with `var(--color-*)` for Zabi scales and stable hex literals for Tailwind default ramps (e.g. red/amber) and white, so consumers processing `theme-only` / `colors` bundles do not hit unresolved `theme()` during Tailwind passes.
1299
+ - **Props typings**: **`Button`**, **`IconButton`**, and **`Tooltip`** prop types now intersect native element attributes (`HTMLButtonAttributes` / `HTMLAttributes<HTMLDivElement>`) so passthrough attributes (`class`, ARIA, `data-*`, etc.) type-check when spread with `...restProps`.
1300
+
1301
+ ### Added
1302
+
1303
+ - **`docs/lucide-icons.md`**: Lists Lucide icons imported by published components for teams using a custom `@lucide/svelte` barrel.
1304
+
1305
+ ### Changed
1306
+
1307
+ - **`scripts/sync-theme-tokens.js`**: End marker after the mirrored base ramp now matches the **`Brand Color Scale - Dark mode variants`** comment (including the “no theme() forward refs” note).
1308
+
1309
+ ### Migration / discovery
1310
+
1311
+ - **`Navbar` → `TopNavbar`**: The older **`Navbar`** export was removed in **6.0.0**; use **`TopNavbar`**. See the **6.0.0** migration notes in this file.
1312
+
1313
+ ## [7.0.0] - 2026-04-19
1314
+
1315
+ ### Breaking changes
1316
+
1317
+ - **`Page`**: Removed the built-in `max-w-4xl` cap. Width is now fully controlled by the parent, or pass e.g. `className="max-w-4xl"` (or use **`Container`** / **`Section`** with `maxWidth` per the layout-width policy).
1318
+ - **`EmptyState`**: Removed the built-in `max-w-lg` cap. Add `className="max-w-lg"` (or another token) if you still want a narrow empty state.
1319
+
1320
+ ### Added
1321
+
1322
+ - **`postinstall`**: After `npm install`, runs `scripts/fix-lucide-svelte-icon-dts.js` to strip a duplicate `type X = ReturnType<typeof X>` line from each `@lucide/svelte` per-icon `dist/icons/*.svelte.d.ts` file. Some TypeScript language services treat the unpatched file as a non-module, which breaks types for deep icon imports and root re-exports.
1323
+ - **`fixedSidebarFlyout`**: New Svelte **action** in the built package (`util/fixed-sidebar-flyout.js`) used by **`SidebarFooter`**. It pins flyouts with `position: fixed` to `[data-sidebar-flyout-anchor]` inside `[data-sidebar-flyout-root]`, teleports the panel to `document.body` while open to avoid scroll clipping, and updates position on scroll/resize. Options: `open`, `align` (`"top"` | `"bottom"`), `gap`, `anchorRole` (`"search"` | `"profile"`).
1324
+
1325
+ ### Changed
1326
+
1327
+ - **`CodeBlock`**: Replaced the ad-hoc copy **`<button>`** with **`IconButton`** (ghost, sm). Copy / copied states use Lucide **`Check`** and **`Copy`** from **`@lucide/svelte/icons/*`**. Wrapper uses **`bg-surface-1`**, **`border-border`**, header **`bg-surface-2`**; code uses **`text-base-900`** on light surfaces instead of **`bg-base-900`** / **`bg-base-*`** chrome with **`text-base-100`** body text.
1328
+ - **`SidebarFooter`**: Profile panel markup restructured with **`data-sidebar-flyout-root`** on the footer host; **`data-sidebar-flyout-anchor="profile"`** on the launcher; the profile snippet is positioned with **`fixedSidebarFlyout`** (`align: "bottom"`, viewport-limited **`max-height`**, **`overflow-y-auto`**, **`overscroll-contain`**). Drops the previous **`absolute bottom-0 left-full`** positioning that misbehaved beside fixed sidebars / scroll ancestors.
1329
+ - **`SidebarNavigation`**: Search trigger wrapped with **`data-sidebar-flyout-anchor="search"`** (collapsed and expanded) so flyouts share the same anchor protocol as the footer profile panel.
1330
+ - **Lucide usage in source**: Prefer **`import Icon from "@lucide/svelte/icons/name"`** over the package barrel for clearer tree-shaking and alignment with the postinstall typings fix. Showcase demos replace **`CircleHelp`** with **`CircleQuestionMark`** (current Lucide export name).
1331
+ - **`zabi-components.css` (generated bundle)**: Picks up additional theme utilities used by the showcase and organisms—e.g. **`--spacing-xs`**, **`text-base-900`**, **`max-h-[min(32rem,calc(100dvh-6rem))]`**, **`overscroll-contain`**, prose/fit/max/min width utilities, responsive **`md:z-10`**, **`md:w-80`**, **`md:flex-nowrap`**, **`md:items-stretch`**, **`overflow-auto`**, **`align-top`**. Some rarely-used utilities were dropped from the scanned set where no longer referenced (e.g. **`left-full`**, base **`items-stretch`** in favor of **`md:items-stretch`**, **`bg-gray-50`**).
1332
+
1333
+ ### Fixed
1334
+
1335
+ - **Lucide + TypeScript**: Consumers importing per-icon modules get consistent typings after install thanks to the **`postinstall`** patch for **`@lucide/svelte`** ~**0.544.x** declaration output.
1336
+
1337
+ ### Migration (from 6.x)
1338
+
1339
+ 1. **`Page` / `EmptyState`**: If layout relied on the old max widths, add them explicitly:
1340
+ - `<Page className="max-w-4xl">` or wrap content in **`Container`** / **`Section`**.
1341
+ - `<EmptyState className="max-w-lg" …>` (or another **`max-w-*`** from your layout policy).
1342
+ 2. **`npm install`**: Keep **`postinstall`** enabled so Lucide icon **`.d.ts`** files are patched; if you use **`npm install --ignore-scripts`**, run `node scripts/fix-lucide-svelte-icon-dts.js` manually or rely on root **`@lucide/svelte`** barrel imports only (may bundle more code).
1343
+ 3. **Demos copying Lucide icon names**: Replace **`CircleHelp`** with **`CircleQuestionMark`** when upgrading Lucide past names that removed **`CircleHelp`**.
1344
+
1345
+ ## [6.0.0] - 2026-04-12
1346
+
1347
+ ### Breaking changes
1348
+
1349
+ - Removed the `Navigation` organism; use `TopNavbar` with `items`, optional `embedded`, or a `nav` snippet instead.
1350
+ - Renamed `SidebarProjectPanel` to `SidebarPanel` (including the `SidebarPanelItem` type).
1351
+
1352
+ ### Added
1353
+
1354
+ - Package subpath exports `zabi-components/colors` and `zabi-components/css` for theme and full bundle CSS.
1355
+ - Published theme documentation files in the package (`THEME.md`, `THEMING.md`, `docs/theme-imports.md`).
1356
+
1357
+ ### Changed
1358
+
1359
+ - Theme build runs `sync:tokens` before CSS generation; added theme output tests (`test:themes`).
1360
+ - Refined default border styling on several components.
1361
+
1362
+ ### Migration
1363
+
1364
+ - Replace `import { Navigation } from 'zabi-components'` (or organisms path) with `TopNavbar` and the documented props/snippets.
1365
+ - Replace `SidebarProjectPanel` imports with `SidebarPanel` and align prop/type names to `SidebarPanelItem` where applicable.
1366
+
1367
+ ## Pending deprecations
1368
+
1369
+ - Legacy deep CSS import paths under `zabi-components/dist/*.css` are still supported for compatibility but documented as legacy. Prefer short exports such as `zabi-components/theme-only`, `zabi-components/theme-dark-only`, `zabi-components/colors`, and `zabi-components/css`.
1370
+
1371
+ ## [5.0.22] - 2026-03-16
1372
+
1373
+ ### ✨ **Features**
1374
+
1375
+ - **List Components**: Added new `List` and `ListItem` components with documentation and usage examples
1376
+ - **Sidebar**: Added `SidebarNavigation` and `SidebarPanel` with improved navigation behaviors
1377
+
1378
+ ### 🔧 **Improvements**
1379
+
1380
+ - **Card Components**: Refactored `Card`, `CardHeader`, and `CardContent` and simplified composition patterns
1381
+ - **Documentation**: Updated Storybook setup and improved component documentation quality
1382
+ - **Config & Environment**: Refined environment variable definitions and release metadata handling
1383
+
1384
+ ### ✅ **No Breaking Changes**
1385
+
1386
+ This is a backward-compatible update.
1387
+
1388
+ ---
1389
+
1390
+ ## [5.0.21] - 2026-01-17
1391
+
1392
+ ### ✨ **Features**
1393
+
1394
+ - **IconButton**: Added a new icon-only button with variants, sizes, and accessible labels
1395
+ - **Exports & Storybook**: Included IconButton in atom exports and Storybook registry
1396
+ - **Docs**: Added IconButton examples to the docs and components showcase
1397
+
1398
+ ### 🔧 **Improvements**
1399
+
1400
+ - **Navigation**: Header navigation now stacks for mobile-friendly layouts
1401
+ - **Select**: Improved selected label truncation and helper message layout
1402
+ - **Button**: Standardized disabled behavior without pointer-events override
1403
+
1404
+ ### ✅ **No Breaking Changes**
1405
+
1406
+ This is a backward-compatible update.
1407
+
1408
+ ---
1409
+
1410
+ ## [5.0.20] - 2025-01-27
1411
+
1412
+ ### 🔧 **Patch Release**
1413
+
1414
+ - **Maintenance**: General improvements and stability updates
1415
+ - **Build**: Enhanced build process and type definitions
1416
+ - **Documentation**: Minor documentation updates
1417
+
1418
+ ### ✅ **No Breaking Changes**
1419
+
1420
+ This is a backward-compatible patch release.
1421
+
1422
+ ---
1423
+
1424
+ ## [5.0.19] - 2025-01-27
1425
+
1426
+ ### 🐛 **Bug Fixes**
1427
+
1428
+ #### **Modal Component**
1429
+ - **Fixed SSR Import Error**: Resolved issue where Modal component was trying to import `focus-utils.js` from a relative path that didn't exist in consuming projects
1430
+ - **Internal Focus Utilities**: Moved focus management utilities (`focus-utils.ts`) to be properly included in the build output
1431
+ - **Build Process**: Added post-build script to fix import paths in built components
1432
+ - This fixes the "Failed to load url ../../routes/lib/focus-utils.js" error that occurred during SSR
1433
+
1434
+ ## [5.0.18] - 2025-01-27
1435
+
1436
+ ### ✨ **Component Enhancements & Documentation**
1437
+
1438
+ #### **Card Component Improvements**
1439
+ - **Compound Components**: Added CardHeader, CardContent, CardFooter, CardTitle, and CardDescription sub-components
1440
+ - Enables flexible card composition with better structure
1441
+ - Maintains backward compatibility with existing title/description/image props
1442
+ - Supports both old and new API patterns
1443
+ - **Enhanced Variants**: Improved elevated, outlined, and flat variants with better hover states
1444
+ - **Accessibility**: Added keyboard navigation (Enter/Space) and proper ARIA attributes for interactive cards
1445
+ - **Size Variants**: Enhanced size support (sm, md, lg) with responsive padding and typography
1446
+
1447
+ #### **Button Component Enhancements**
1448
+ - **New Variants**: Added ghost, outline, and link variants for more design flexibility
1449
+ - **Improved Focus States**: Enhanced focus ring styles for better accessibility
1450
+ - **Size Refinements**: Improved size variants with better typography and spacing
1451
+ - **Better Disabled States**: Enhanced disabled state styling and interaction
1452
+
1453
+ #### **Badge Component Updates**
1454
+ - **Icon Support**: Added optional icon display for success, warning, error, and info variants
1455
+ - **Size Variants**: Enhanced size support with responsive icon sizing
1456
+ - **Visual Consistency**: Improved color system integration
1457
+
1458
+ #### **Component Accessibility Improvements**
1459
+ - **Modal Component**: Enhanced focus management and keyboard navigation
1460
+ - **Dropdown Component**: Improved ARIA attributes and keyboard navigation
1461
+ - **Card Component**: Added proper roles and keyboard handlers for interactive cards
1462
+ - **Focus Utilities**: New focus-utils.ts for better focus management across components
1463
+
1464
+ #### **Documentation Additions**
1465
+ - **ACCESSIBILITY.md**: Comprehensive accessibility documentation with component-by-component analysis
1466
+ - **KEYBOARD_NAVIGATION.md**: Detailed keyboard navigation patterns and best practices
1467
+ - **VARIANTS.md**: Complete variant system documentation
1468
+ - **THEMING.md**: Enhanced theming guide with customization examples
1469
+ - **THEME_QUICK_REFERENCE.md**: Quick reference for theme customization
1470
+ - **SHADCN_INSPIRATION.md**: Design system inspiration and improvement roadmap
1471
+
1472
+ #### **Theme & Styling Improvements**
1473
+ - **Color System**: Enhanced color variable resolution and consistency
1474
+ - **Theme Files**: Updated theme CSS files with improved structure
1475
+ - **Build Process**: Enhanced CSS build scripts for better theme processing
1476
+
1477
+ #### **Build & Development**
1478
+ - **Type Definitions**: Updated TypeScript definitions for new components
1479
+ - **Component Exports**: Added exports for new Card compound components
1480
+ - **Storybook**: Enhanced Card stories with compound component examples
1481
+
1482
+ ### ✅ **No Breaking Changes**
1483
+
1484
+ This is a backward-compatible update. Existing component usage continues to work, with new features available as optional enhancements.
1485
+
1486
+ ---
1487
+
1488
+ ## [5.0.17] - 2025-01-27
1489
+
1490
+ ### 🔧 **Patch Release**
1491
+
1492
+ - **Documentation**: Updated changelog with version 5.0.16 release notes
1493
+ - **Build Improvements**: Adjusted theme validation script to be less strict for dark theme structure differences
1494
+ - **Version Bump**: Patch release for improved stability
1495
+
1496
+ ---
1497
+
1498
+ ## [5.0.16] - 2025-01-27
1499
+
1500
+ ### 🎨 **Color System & Component Styling Updates**
1501
+
1502
+ #### **Color System Refactoring**
1503
+ - **Enhanced Color Variables**: Improved color system with better CSS variable resolution
1504
+ - **Theme Updates**: Updated theme files with improved color consistency
1505
+ - **Build Process**: Enhanced CSS build scripts for better color processing
1506
+
1507
+ #### **Component Style Improvements**
1508
+ - **Checkbox Component**: Updated styling to match M3 design system with improved visual consistency
1509
+ - **Input Component**: Enhanced color system integration and styling updates
1510
+ - **Multiple Components**: Updated Button, CodeBlock, ColorPicker, FeatureCard, Progress, Skeleton, Textarea, Toast, Toggle, and more with consistent styling
1511
+ - **Molecule Components**: Updated Alert, ComponentDemo, Dropdown, ImageUpload, Modal, SlideUp, and Tabs with improved styling
1512
+
1513
+ #### **Storybook Configuration**
1514
+ - **Story Organization**: Reorganized stories into atoms/, molecules/, and organisms/ folders for better structure
1515
+ - **Enhanced Stories**: Updated component stories with improved examples and controls
1516
+ - **Configuration Updates**: Enhanced Storybook configuration for better development experience
1517
+
1518
+ #### **Build & Dependency Updates**
1519
+ - **Dependency Updates**: Updated package dependencies for improved compatibility
1520
+ - **Build Scripts**: Enhanced CSS build and cleanup scripts
1521
+ - **Type Definitions**: Updated TypeScript definitions for better type safety
1522
+
1523
+ ### ✅ **No Breaking Changes**
1524
+
1525
+ This is a backward-compatible update focusing on styling improvements and build enhancements.
1526
+
1527
+ ---
1528
+
1529
+ ## [5.0.15] - 2025-01-27
1530
+
1531
+ ### 🎨 **Color System Improvements**
1532
+
1533
+ #### **Standalone Colors File**
1534
+ - **New Export**: Added `zabi-components-colors.css` as a separate export for consumer apps
1535
+ - Contains resolved CSS custom properties that work without Tailwind CSS processing
1536
+ - Perfect for apps not using Tailwind CSS v4
1537
+ - Includes all color variables with resolved hex values
1538
+ - Supports both light and dark mode variants
1539
+ - **Package Export**: Added `./dist/zabi-components-colors.css` to package.json exports
1540
+ - **Usage**: Consumer apps can now import colors independently:
1541
+ ```css
1542
+ /* For Tailwind v4 users */
1543
+ @import "zabi-components/dist/zabi-components.css";
1544
+
1545
+ /* For non-Tailwind users */
1546
+ @import "zabi-components/dist/zabi-components-colors.css";
1547
+ ```
1548
+
1549
+ #### **Component Cleanup**
1550
+ - **Badge Component**: Removed all arbitrary Tailwind values (`[var(--color-*)]`)
1551
+ - Now uses proper utility classes: `bg-success border-success` instead of `bg-success border-[var(--color-success)]`
1552
+ - More maintainable and consistent code
1553
+ - **Added Background Utilities**: Created explicit background utility classes to match border utilities
1554
+ - Added: `bg-success`, `bg-warning`, `bg-error`, `bg-info`, `bg-neutral`, `bg-energetic`, `bg-secondary`
1555
+ - Ensures consistent styling across all components
1556
+
1557
+ #### **Build Improvements**
1558
+ - **Simplified Build Script**: Cleaned up CSS build process
1559
+ - **Better Separation**: Colors file is now separate from main CSS, making it easier to import independently
1560
+
1561
+ ### 🎯 **Technical Details**
1562
+
1563
+ - **Color System**: Colors now work correctly in consumer apps regardless of Tailwind CSS setup
1564
+ - **Code Quality**: Removed all arbitrary value syntax for better maintainability
1565
+ - **Consistency**: All color utilities now follow the same pattern
1566
+
1567
+ ### ✅ **No Breaking Changes**
1568
+
1569
+ This is a backward-compatible update. Existing usage continues to work, with improved color system support.
1570
+
1571
+ ---
1572
+
1573
+ ## [5.0.14] - 2025-01-27
1574
+
1575
+ ### ✨ **Component Improvements**
1576
+
1577
+ #### **Textarea Component**
1578
+ - **Visual Consistency**: Updated Textarea styling to match Input component exactly
1579
+ - Changed background to `bg-brand-100` (matching Input)
1580
+ - Updated border radius from `rounded-md` to `rounded-lg`
1581
+ - Removed border for default variant (matches Input's borderless design)
1582
+ - Updated focus states to use `focus:ring-2 focus:ring-brand-500` with `focus:ring-offset-0`
1583
+ - Standardized placeholder and text colors to match Input
1584
+ - **Simplified API**: Removed `size` prop - Textarea now uses a fixed size (matching Input's default "md" size)
1585
+ - **Enhanced Features**: Added message support with icons for success, warning, and error variants (matching Input component)
1586
+
1587
+ #### **Storybook Updates**
1588
+ - **Storybook 10 Compatibility**: Standardized `argTypes` controls across all stories
1589
+ - Updated Alert, Badge, Toggle, Tooltip, and SlideUp stories to use string shorthand for controls
1590
+ - Improved consistency and cleaner code
1591
+ - **Textarea Stories**: Added message examples for success, warning, and error variants
1592
+ - **Removed Deprecated Stories**: Removed Small and Large Textarea stories (size prop removed)
1593
+
1594
+ ### 🎯 **Technical Details**
1595
+
1596
+ - **Styling Alignment**: Textarea now visually matches Input component for consistent form design
1597
+ - **API Simplification**: Removed unnecessary size variations from Textarea
1598
+ - **Storybook Modernization**: All stories now use Storybook 10 best practices
1599
+
1600
+ ### ✅ **No Breaking Changes**
1601
+
1602
+ This is a backward-compatible update. Existing Textarea usage continues to work, with improved visual consistency.
1603
+
1604
+ ---
1605
+
1606
+ ## [4.0.1] - 2025-01-27
1607
+
1608
+ ### 🛡️ **SSR Safety Overhaul - "Zero Runtime Errors" Edition**
1609
+
1610
+ #### ✅ **Critical SSR Issues Resolved**
1611
+
1612
+ This release completely eliminates the "Cannot read properties of null (reading 'f')" error and ensures 100% SSR safety across all components.
1613
+
1614
+ #### 🔧 **SSR Safety Improvements**
1615
+
1616
+ ##### **Route Navigation Fixed**
1617
+ - **Replaced `window.location.href`**: All route pages now use SvelteKit's `goto()` from `$app/navigation`
1618
+ - **SSR-Safe Navigation**: No more direct window access during server-side rendering
1619
+ - **Files Updated**: `src/routes/+page.svelte`, `src/routes/docs/+page.svelte`
1620
+
1621
+ ##### **Component Lifecycle Fixed**
1622
+ - **ThemeToggle Component**: Replaced `$effect` with `onMount` for initial setup
1623
+ - **SSRSafe Component**: Simplified checks to rely solely on `mounted` state
1624
+ - **Eliminated Race Conditions**: Fixed timing issues during hydration
1625
+
1626
+ ##### **ID Generation Fixed**
1627
+ - **Input Component**: Moved ID generation to `onMount` to prevent hydration mismatches
1628
+ - **Checkbox Component**: Moved ID generation to `onMount` for consistency
1629
+ - **SSR-Safe IDs**: No more SSR/client ID mismatches
1630
+
1631
+ ##### **Enhanced SSR Utilities**
1632
+ - **New `ssr-safe.ts`**: Comprehensive SSR-safe utility functions
1633
+ - **SvelteKit Integration**: Uses `browser` from `$app/environment`
1634
+ - **Safe API Access**: `safeLocalStorage()`, `safeDocument()`, `safeWindow()`
1635
+ - **ID Generation**: `generateId()` function for consistent SSR-safe IDs
1636
+
1637
+ #### 🎯 **Technical Improvements**
1638
+
1639
+ ##### **Consistent SSR Pattern**
1640
+ ```svelte
1641
+ import { safeLocalStorage, safeDocument } from "../../lib/ssr-safe";
1642
+
1643
+ onMount(() => {
1644
+ mounted = true;
1645
+ const storage = safeLocalStorage();
1646
+ if (storage) {
1647
+ // Safe browser API access
1648
+ }
1649
+ });
1650
+ ```
1651
+
1652
+ ##### **SvelteKit Best Practices**
1653
+ - Uses `browser` from `$app/environment` instead of manual checks
1654
+ - Uses `goto()` from `$app/navigation` for routing
1655
+ - Follows SvelteKit SSR patterns throughout
1656
+
1657
+ ##### **Hydration Safety**
1658
+ - No more SSR/client mismatches
1659
+ - Consistent ID generation across server and client
1660
+ - Proper lifecycle management
1661
+
1662
+ #### ✅ **Results Achieved**
1663
+
1664
+ ##### **Build Status**
1665
+ - ✅ **Successful Builds**: All builds complete without errors
1666
+ - ✅ **Type Safety**: 0 TypeScript errors in zabi-components code
1667
+ - ✅ **SSR Compatible**: Full server-side rendering support
1668
+
1669
+ ##### **Runtime Safety**
1670
+ - ✅ **Zero Runtime Errors**: Eliminated all "Cannot read properties of null" errors
1671
+ - ✅ **Hydration Safe**: No more client/server mismatches
1672
+ - ✅ **Theme Toggle**: Works correctly on first load
1673
+ - ✅ **Form Inputs**: Stable IDs across SSR/hydration
1674
+
1675
+ ##### **Cross-Environment Compatibility**
1676
+ - ✅ **SvelteKit SSR**: Full compatibility with server-side rendering
1677
+ - ✅ **Client Hydration**: Smooth hydration without errors
1678
+ - ✅ **Production Ready**: Works correctly in production environments
1679
+
1680
+ #### 📋 **Files Modified**
1681
+
1682
+ 1. `src/routes/+page.svelte` - Navigation fixes (6 instances)
1683
+ 2. `src/routes/docs/+page.svelte` - Navigation fixes (2 instances)
1684
+ 3. `src/components/atoms/ThemeToggle.svelte` - SSR safety improvements
1685
+ 4. `src/components/atoms/Input.svelte` - ID generation fixes
1686
+ 5. `src/components/atoms/Checkbox.svelte` - ID generation fixes
1687
+ 6. `src/components/SSRSafe.svelte` - Simplified checks
1688
+ 7. `src/lib/ssr-safe.ts` - Enhanced SSR utilities
1689
+
1690
+ #### 🚀 **Performance Benefits**
1691
+
1692
+ - **Faster Builds**: Reduced build time and complexity
1693
+ - **Better Reliability**: No more runtime crashes
1694
+ - **Improved DX**: Better developer experience with consistent patterns
1695
+ - **Production Ready**: Fully tested in production environments
1696
+
1697
+ #### 🔄 **Migration Notes**
1698
+
1699
+ **No Breaking Changes**: This is a backward-compatible update that fixes SSR issues without changing component APIs.
1700
+
1701
+ **For Developers**: All existing component usage remains the same - the fixes are internal and transparent to users.
1702
+
1703
+ ---
1704
+
1705
+ ## [2.1.0] - 2025-01-27
1706
+
1707
+ ### 🚀 Major Event Handling Refactor - "Cross-Framework Compatible" Edition
1708
+
1709
+ #### ⚠️ **BREAKING CHANGES** - Event Handling Overhaul
1710
+
1711
+ This is a **major breaking change** that affects how all components handle events. The refactoring eliminates SSR/production errors and makes components compatible with React, Vue, and vanilla JavaScript applications.
1712
+
1713
+ #### 🔧 **Event System Refactoring**
1714
+ - **Removed `createEventDispatcher`**: Eliminated all `createEventDispatcher` usage across 18 components
1715
+ - **Implemented Event Forwarding**: All components now use `{...$$restProps}` for native DOM event forwarding
1716
+ - **SSR-Safe**: Fixed hydration mismatches and production build errors
1717
+ - **Cross-Framework Compatible**: Components now work in React, Vue, Svelte, and vanilla JS
1718
+
1719
+ #### 🎯 **Components Refactored**
1720
+
1721
+ ##### **Form Components**
1722
+ - **Input**: Replaced `bind:value` with `value` prop + `on:input` handler
1723
+ - **Textarea**: Replaced `bind:value` with `value` prop + `on:input` handler
1724
+ - **Select**: Replaced `bind:value` with `value` prop + `on:change` handler
1725
+ - **Checkbox**: Replaced `bind:checked` with `checked` prop + `on:change` handler
1726
+
1727
+ ##### **Interactive Components**
1728
+ - **Button**: Added `{...$$restProps}` for native event forwarding
1729
+ - **Toggle**: Added event handler + `{...$$restProps}` for cross-framework compatibility
1730
+ - **ThemeToggle**: Added `{...$$restProps}` for event forwarding
1731
+
1732
+ ##### **Complex Components**
1733
+ - **Modal**: Removed `createEventDispatcher`, simplified event handling
1734
+ - **Tabs**: Removed `createEventDispatcher`, uses direct state management
1735
+ - **ImageUpload**: Removed `createEventDispatcher`, simplified file handling
1736
+ - **ContactForm**: Updated to use new event patterns
1737
+ - **Form**: Removed `createEventDispatcher`, added `{...$$restProps}`
1738
+ - **Alert**: Removed `createEventDispatcher`, added `{...$$restProps}`
1739
+ - **Toast**: Removed `createEventDispatcher`, simplified close handling
1740
+ - **Badge**: Removed `createEventDispatcher`, added `{...$$restProps}`
1741
+ - **SlideUp**: Removed `createEventDispatcher`, simplified modal handling
1742
+ - **TopNavbar**: Removed `createEventDispatcher`, added `{...$$restProps}`
1743
+ - **Navigation**: Removed `createEventDispatcher`, simplified navigation
1744
+
1745
+ #### ✅ **Benefits Achieved**
1746
+
1747
+ ##### **SSR/Production Safe**
1748
+ - ✅ **No more hydration errors** in SSR environments
1749
+ - ✅ **Production builds work correctly** without runtime errors
1750
+ - ✅ **Eliminated `createEventDispatcher` errors** in SvelteKit applications
1751
+
1752
+ ##### **Cross-Framework Compatible**
1753
+ - ✅ **React Applications**: Components work with standard React event handlers
1754
+ - ✅ **Vue Applications**: Compatible with Vue's event system
1755
+ - ✅ **Vanilla JavaScript**: Full compatibility with native DOM events
1756
+ - ✅ **Svelte/SvelteKit**: Maintains full compatibility with improved reliability
1757
+
1758
+ ##### **Standards Compliant**
1759
+ - ✅ **Native DOM Events**: Uses web standards instead of framework-specific events
1760
+ - ✅ **Better Performance**: Reduced JavaScript overhead and bundle size
1761
+ - ✅ **More Predictable**: Event handling follows web standards
1762
+
1763
+ #### 🔄 **Migration Required**
1764
+
1765
+ **Before (Old Pattern):**
1766
+ ```svelte
1767
+ <!-- This would cause SSR errors -->
1768
+ <Button on:click={handleClick}>Click me</Button>
1769
+ <Input bind:value={inputValue} />
1770
+ <Modal bind:isOpen onclick={handleClose} />
1771
+ ```
1772
+
1773
+ **After (New Pattern):**
1774
+ ```svelte
1775
+ <!-- Now works in all environments -->
1776
+ <Button on:click={handleClick}>Click me</Button>
1777
+ <Input value={inputValue} on:input={(e) => inputValue = e.target.value} />
1778
+ <Modal bind:isOpen on:click={handleClose} />
1779
+ ```
1780
+
1781
+ #### 📋 **Breaking Changes Summary**
1782
+
1783
+ 1. **Form Components**: Must use `value` prop + event handlers instead of `bind:value`
1784
+ 2. **Event Names**: Some custom events replaced with native DOM events
1785
+ 3. **Event Structure**: Simplified event structures across all components
1786
+ 4. **SSR Compatibility**: Components now work correctly in SSR environments
1787
+
1788
+ #### 🛠️ **Technical Details**
1789
+
1790
+ - **Event Forwarding**: All interactive elements now use `{...$$restProps}`
1791
+ - **Native Events**: Replaced custom events with standard DOM events where possible
1792
+ - **State Management**: Simplified internal state management
1793
+ - **Type Safety**: Maintained full TypeScript support with updated types
1794
+
1795
+ #### 🎯 **Migration Guide**
1796
+
1797
+ See the updated README.md for detailed migration examples and new usage patterns.
1798
+
1799
+ ---
1800
+
1801
+ ## [2.0.2] - 2025-01-27
1802
+
1803
+ ### 🎯 Major Simplification - "Less is More" Edition
1804
+
1805
+ #### ✨ Simplified Components
1806
+ - **Massive Code Reduction**: Reduced component complexity by 60-80% across the board
1807
+ - **Modern CSS-First Approach**: Replaced complex JavaScript with modern CSS-only solutions
1808
+ - **Unified Form Component**: Consolidated 4 form components into 1 simple Form component
1809
+ - **Removed Complex Managers**: Eliminated NotificationManager, ToastManager, and other complex utilities
1810
+
1811
+ #### 🔧 Atom Components Simplified
1812
+ - **Badge**: Removed complex variants, icons, and animations - now just basic color variants
1813
+ - **Button**: Simplified to 3 variants (primary, secondary, danger) with minimal props
1814
+ - **Card**: Merged ModernCard into Card, removed complex styling and animations
1815
+ - **Checkbox**: Removed complex validation, sizing, and custom CSS
1816
+ - **Input**: Simplified to essential props only, removed complex validation system
1817
+ - **Heading**: Clean heading component with just level and text props
1818
+ - **Progress**: Basic progress bar with simple percentage display
1819
+ - **Select**: Simplified dropdown with basic options support
1820
+ - **Textarea**: Essential textarea with minimal configuration
1821
+ - **Toggle**: Fixed-size toggle with simple on/off state
1822
+ - **ThemeToggle**: Emoji-based theme switcher, removed complex styling
1823
+ - **Toast**: Fixed-position toast with basic message display
1824
+ - **Tooltip**: CSS-only positioning with data-placement attributes
1825
+ - **Skeleton**: Simple loading placeholder with basic styling
1826
+ - **TextAlignment**: Emoji-based alignment selector
1827
+ - **OptimizedImage**: Basic image component with lazy loading
1828
+ - **ColorPicker**: Simple color grid without complex dropdown logic
1829
+
1830
+ #### 🧩 Molecule Components Simplified
1831
+ - **Dropdown**: CSS-only positioning, removed complex JavaScript calculations
1832
+ - **ImageUpload**: Direct file selection, removed modal and progress bars
1833
+ - **Form**: Unified form component, removed complex validation and field management
1834
+ - **Modal**: Simple modal with basic backdrop and close functionality
1835
+ - **SlideUp**: CSS-only slide-up panel, removed complex state management
1836
+ - **Tabs**: Basic tab navigation with keyboard support, removed complex utilities
1837
+
1838
+ #### 🏗️ Organism Components Simplified
1839
+ - **TopNavbar**: Responsive top navbar with mobile menu, removed complex variants
1840
+ - **Navigation**: Simple navigation list with active state
1841
+ - **Removed Complex Components**: Deleted NavigationItem, NotificationManager, ToastManager
1842
+
1843
+ #### 🗑️ Removed Complexity
1844
+ - **External Utility Functions**: Removed complex tabs-utils, navigation-utils
1845
+ - **Complex Type Definitions**: Simplified event types and interfaces
1846
+ - **Svelte Transitions**: Replaced with CSS-only animations
1847
+ - **Custom CSS Variables**: Replaced with standard Tailwind classes
1848
+ - **Complex State Management**: Simplified to essential state only
1849
+ - **Redundant Folders**: Removed simple/ folder with duplicate exports
1850
+
1851
+ #### 🎨 Modern CSS Features
1852
+ - **CSS-only Positioning**: Using `position: absolute` and `transform` for tooltips and dropdowns
1853
+ - **CSS Pseudo-elements**: Using `::before` for arrows and indicators
1854
+ - **CSS Data Attributes**: Using `data-placement` for component positioning
1855
+ - **CSS Transitions**: Smooth animations handled entirely by CSS
1856
+ - **Tailwind-First**: All styling using Tailwind utility classes
1857
+
1858
+ #### 📦 Package Improvements
1859
+ - **Cleaner Structure**: Removed unnecessary abstraction layers
1860
+ - **Direct Imports**: Components available from their natural locations
1861
+ - **Simplified APIs**: Just the essential props needed for functionality
1862
+ - **Better Performance**: Reduced JavaScript overhead and complexity
1863
+
1864
+ #### 🐛 Bug Fixes
1865
+ - **Accessibility**: Fixed label associations and ARIA attributes
1866
+ - **TypeScript**: Resolved all type errors and warnings
1867
+ - **Linting**: Fixed all ESLint and Svelte warnings
1868
+ - **Deprecation**: Replaced deprecated Lucide icons with emoji alternatives
1869
+
1870
+ ### Breaking Changes
1871
+ - **Removed Components**: KeyValueForm, ContactForm, ModernForm, NotificationManager, ToastManager
1872
+ - **Simplified Props**: Many components now have fewer configuration options
1873
+ - **Removed Variants**: Reduced complex variant systems to essential options only
1874
+ - **Event Simplification**: Simplified event structures across all components
1875
+
1876
+ ### Migration Guide
1877
+ - **Form Components**: Use the unified `Form` component instead of multiple form variants
1878
+ - **Complex Features**: Many complex features have been removed in favor of simplicity
1879
+ - **Styling**: Components now use standard Tailwind classes instead of custom CSS variables
1880
+ - **Imports**: Import components directly from atoms/, molecules/, organisms/ folders
1881
+
1882
+ ## [1.1.1] - 2025-10-06
1883
+
1884
+ ### Fixed
1885
+ - Updated generated TypeScript definitions for better type safety
1886
+ - Enhanced package structure with improved type exports
1887
+ - Fixed build consistency and versioning
1888
+
1889
+ ### Changed
1890
+ - Updated internal build files and generated assets
1891
+ - Improved TypeScript declaration file generation
1892
+ - Enhanced package.json structure and exports
1893
+
1894
+ ## [1.1.0] - 2025-10-05
1895
+
1896
+ ### Added
1897
+ - Comprehensive TypeScript definitions built into the library
1898
+ - Self-contained type system - no custom type definitions needed in consumer apps
1899
+ - Enhanced component type safety with proper event handling
1900
+ - SSR-safe utilities for better SvelteKit compatibility
1901
+
1902
+ ### Changed
1903
+ - Updated build configuration for proper SvelteKit compatibility
1904
+ - Removed SSR mode from library build (libraries should be client-side)
1905
+ - Enhanced package.json exports for better module resolution
1906
+ - Updated peer dependencies for Svelte 5 compatibility
1907
+
1908
+ ### Fixed
1909
+ - Fixed runtime errors with `Cannot read properties of null (reading 'f')`
1910
+ - Fixed event handling for Svelte 5 compatibility
1911
+ - Fixed TypeScript type inference for component props and events
1912
+ - Fixed package exports for proper module resolution
1913
+
1914
+ ### Removed
1915
+ - Removed need for custom type definitions in consumer applications
1916
+ - Removed unnecessary client-only component duplicates
1917
+ - Removed SSR wrapper components (not needed)
1918
+
1919
+ ### Breaking Changes
1920
+ - None - this is a backward-compatible update
1921
+
1922
+ ## [1.0.11] - Previous version
1923
+ - Initial release with basic components
1924
+