zabi-components 8.0.0 → 9.0.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.
- package/CHANGELOG.md +2935 -0
- package/README.md +243 -10
- package/THEME.md +162 -332
- package/THEMING.md +1419 -259
- package/dist/atoms/ActionPanel.svelte +15 -5
- package/dist/atoms/Avatar.svelte +120 -0
- package/dist/atoms/Avatar.svelte.d.ts +46 -0
- package/dist/atoms/Badge.svelte +71 -13
- package/dist/atoms/Badge.svelte.d.ts +8 -2
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +217 -30
- package/dist/atoms/Button.svelte.d.ts +33 -4
- package/dist/atoms/Card.svelte +182 -26
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/CardHeader.svelte +16 -4
- package/dist/atoms/Checkbox.svelte +81 -21
- package/dist/atoms/Checkbox.svelte.d.ts +31 -2
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeBlock.svelte +20 -2
- package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +260 -49
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
- package/dist/atoms/Container.svelte +16 -8
- package/dist/atoms/DateField.svelte +145 -0
- package/dist/atoms/DateField.svelte.d.ts +81 -0
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +176 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +63 -5
- package/dist/atoms/Heading.svelte.d.ts +14 -0
- package/dist/atoms/IconButton.svelte +294 -26
- package/dist/atoms/IconButton.svelte.d.ts +62 -4
- package/dist/atoms/Input.svelte +259 -53
- package/dist/atoms/Input.svelte.d.ts +33 -2
- package/dist/atoms/List.svelte +5 -1
- package/dist/atoms/ListItem.svelte +70 -13
- package/dist/atoms/ListItem.svelte.d.ts +11 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +99 -19
- package/dist/atoms/Progress.svelte.d.ts +15 -1
- package/dist/atoms/Radio.svelte +51 -3
- package/dist/atoms/Radio.svelte.d.ts +30 -2
- package/dist/atoms/Rating.svelte +620 -0
- package/dist/atoms/Rating.svelte.d.ts +77 -0
- package/dist/atoms/Select.svelte +438 -98
- package/dist/atoms/Select.svelte.d.ts +61 -3
- package/dist/atoms/SelectionControl.svelte +193 -23
- package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
- package/dist/atoms/Skeleton.svelte +114 -18
- package/dist/atoms/Skeleton.svelte.d.ts +30 -3
- package/dist/atoms/SkeletonGroup.svelte +43 -0
- package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
- package/dist/atoms/Slider.svelte +344 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Sparkline.svelte +109 -0
- package/dist/atoms/Sparkline.svelte.d.ts +27 -0
- package/dist/atoms/SpeechBubble.svelte +187 -0
- package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +151 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +472 -0
- package/dist/atoms/TemporalField.svelte.d.ts +48 -0
- package/dist/atoms/Text.svelte +23 -11
- package/dist/atoms/Text.svelte.d.ts +6 -3
- package/dist/atoms/Textarea.svelte +150 -44
- package/dist/atoms/Textarea.svelte.d.ts +35 -2
- package/dist/atoms/ThemeToggle.svelte +223 -44
- package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +151 -0
- package/dist/atoms/TimeField.svelte.d.ts +85 -0
- package/dist/atoms/Toast.svelte +22 -8
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +98 -14
- package/dist/atoms/Toggle.svelte.d.ts +26 -2
- package/dist/atoms/Tooltip.svelte +528 -24
- package/dist/atoms/Tooltip.svelte.d.ts +18 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +38 -0
- package/dist/atoms/index.js +29 -0
- package/dist/atoms/selection-control.styles.d.ts +47 -5
- package/dist/atoms/selection-control.styles.js +52 -10
- package/dist/components/atoms/index.d.ts +38 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +47 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +104 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/map.d.ts +11 -0
- package/dist/components/map.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +51 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +83 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +10 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +24 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +79 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +39 -0
- package/dist/components/util/avatar.d.ts.map +1 -0
- package/dist/components/util/bottom-sheet.d.ts +30 -0
- package/dist/components/util/bottom-sheet.d.ts.map +1 -0
- package/dist/components/util/bottom-tab-bar.d.ts +41 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
- package/dist/components/util/calendar.d.ts +56 -0
- package/dist/components/util/calendar.d.ts.map +1 -0
- package/dist/components/util/chart.d.ts +140 -0
- package/dist/components/util/chart.d.ts.map +1 -0
- package/dist/components/util/chip.d.ts +66 -0
- package/dist/components/util/chip.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts.map +1 -1
- package/dist/components/util/code-field.d.ts +63 -0
- package/dist/components/util/code-field.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +64 -0
- package/dist/components/util/collapsible.d.ts.map +1 -0
- package/dist/components/util/confirm-dialog.d.ts +12 -0
- package/dist/components/util/confirm-dialog.d.ts.map +1 -0
- package/dist/components/util/date.d.ts +96 -0
- package/dist/components/util/date.d.ts.map +1 -0
- package/dist/components/util/display-voice.d.ts +10 -0
- package/dist/components/util/display-voice.d.ts.map +1 -0
- package/dist/components/util/drawer.d.ts +13 -0
- package/dist/components/util/drawer.d.ts.map +1 -0
- package/dist/components/util/dropdown.d.ts +47 -0
- package/dist/components/util/dropdown.d.ts.map +1 -0
- package/dist/components/util/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +45 -0
- package/dist/components/util/field.d.ts.map +1 -0
- package/dist/components/util/fit-in-viewport.d.ts +201 -0
- package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +77 -2
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/hydration.d.ts +45 -0
- package/dist/components/util/hydration.d.ts.map +1 -0
- package/dist/components/util/keyboard-inset.d.ts +18 -0
- package/dist/components/util/keyboard-inset.d.ts.map +1 -0
- package/dist/components/util/map-view.d.ts +144 -0
- package/dist/components/util/map-view.d.ts.map +1 -0
- package/dist/components/util/media-grid.d.ts +51 -0
- package/dist/components/util/media-grid.d.ts.map +1 -0
- package/dist/components/util/media.d.ts +6 -0
- package/dist/components/util/media.d.ts.map +1 -0
- package/dist/components/util/overlay-chrome.d.ts +53 -0
- package/dist/components/util/overlay-chrome.d.ts.map +1 -0
- package/dist/components/util/overlay.d.ts +72 -0
- package/dist/components/util/overlay.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +234 -0
- package/dist/components/util/photo.d.ts.map +1 -0
- package/dist/components/util/portal.d.ts +23 -0
- package/dist/components/util/portal.d.ts.map +1 -0
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.d.ts.map +1 -0
- package/dist/components/util/pull-to-refresh.d.ts +27 -0
- package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
- package/dist/components/util/radio-keys.d.ts +25 -0
- package/dist/components/util/radio-keys.d.ts.map +1 -0
- package/dist/components/util/rating.d.ts +23 -0
- package/dist/components/util/rating.d.ts.map +1 -0
- package/dist/components/util/ready-made-strings.d.ts +77 -0
- package/dist/components/util/ready-made-strings.d.ts.map +1 -0
- package/dist/components/util/scroll-edge.d.ts +33 -0
- package/dist/components/util/scroll-edge.d.ts.map +1 -0
- package/dist/components/util/scroll-padding.d.ts +20 -0
- package/dist/components/util/scroll-padding.d.ts.map +1 -0
- package/dist/components/util/segmented-control.d.ts +16 -0
- package/dist/components/util/segmented-control.d.ts.map +1 -0
- package/dist/components/util/select.d.ts +41 -0
- package/dist/components/util/select.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +84 -0
- package/dist/components/util/sheet-drag.d.ts.map +1 -0
- package/dist/components/util/sidebar.d.ts +63 -0
- package/dist/components/util/sidebar.d.ts.map +1 -0
- package/dist/components/util/skeleton.d.ts +15 -0
- package/dist/components/util/skeleton.d.ts.map +1 -0
- package/dist/components/util/sliding-indicator.d.ts +25 -0
- package/dist/components/util/sliding-indicator.d.ts.map +1 -0
- package/dist/components/util/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/stepper.d.ts +40 -0
- package/dist/components/util/stepper.d.ts.map +1 -0
- package/dist/components/util/swipeable-list-item.d.ts +36 -0
- package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
- package/dist/components/util/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +133 -0
- package/dist/components/util/toaster.d.ts.map +1 -0
- package/dist/components/util/tone-surface.d.ts +34 -0
- package/dist/components/util/tone-surface.d.ts.map +1 -0
- package/dist/components/util/tooltip.d.ts +10 -0
- package/dist/components/util/tooltip.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +35 -0
- package/dist/components/util/top-navbar.d.ts.map +1 -0
- package/dist/components/util/touch-target.d.ts +31 -0
- package/dist/components/util/touch-target.d.ts.map +1 -0
- package/dist/components/util/zabi-strings.d.ts +142 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +173 -0
- package/dist/create-theme/index.d.ts +190 -0
- package/dist/create-theme/index.js +1034 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +238 -0
- package/dist/create-theme/theme-data.js +2644 -0
- package/dist/index.d.ts +104 -2
- package/dist/index.js +75 -2
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +63 -16
- package/dist/molecules/Alert.svelte.d.ts +14 -1
- package/dist/molecules/AppBar.svelte +674 -0
- package/dist/molecules/AppBar.svelte.d.ts +131 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +608 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
- package/dist/molecules/BottomTabBar.svelte +403 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +347 -0
- package/dist/molecules/Collapsible.svelte.d.ts +61 -0
- package/dist/molecules/CollapsibleGroup.svelte +136 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +16 -4
- package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
- package/dist/molecules/ConfirmDialog.svelte +257 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
- package/dist/molecules/ContactForm.svelte +34 -17
- package/dist/molecules/ContactForm.svelte.d.ts +8 -0
- package/dist/molecules/Drawer.svelte +415 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +517 -48
- package/dist/molecules/Dropdown.svelte.d.ts +47 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +55 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/FormField.svelte +9 -1
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +492 -69
- package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
- package/dist/molecules/LineChart.svelte +459 -0
- package/dist/molecules/LineChart.svelte.d.ts +53 -0
- package/dist/molecules/MapView.svelte +565 -0
- package/dist/molecules/MapView.svelte.d.ts +88 -0
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +293 -72
- package/dist/molecules/Modal.svelte.d.ts +51 -2
- package/dist/molecules/NavigationMenuContent.svelte +58 -1
- package/dist/molecules/NavigationMenuItem.svelte +0 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuList.svelte +10 -3
- package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
- package/dist/molecules/Page.svelte +32 -2
- package/dist/molecules/Page.svelte.d.ts +11 -0
- package/dist/molecules/PhotoGrid.svelte +523 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
- package/dist/molecules/PhotoViewer.svelte +1213 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/PropsTable.svelte +22 -10
- package/dist/molecules/PropsTable.svelte.d.ts +3 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +72 -18
- package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
- package/dist/molecules/Section.svelte +1 -1
- package/dist/molecules/SegmentedControl.svelte +456 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
- package/dist/molecules/SidebarBrandHeader.svelte +11 -1
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
- package/dist/molecules/SidebarFooter.svelte +30 -7
- package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SlideUp.svelte +220 -61
- package/dist/molecules/SlideUp.svelte.d.ts +26 -0
- package/dist/molecules/SortableList.svelte +633 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +562 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +250 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +310 -9
- package/dist/molecules/Tabs.svelte.d.ts +7 -0
- package/dist/molecules/Toaster.svelte +514 -5
- package/dist/molecules/Toaster.svelte.d.ts +153 -2
- package/dist/molecules/ToasterToast.svelte +495 -77
- package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
- package/dist/molecules/UnsavedChangesBar.svelte +223 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +51 -0
- package/dist/molecules/index.js +31 -0
- package/dist/molecules/toast-store.d.ts +83 -7
- package/dist/molecules/toast-store.js +25 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +696 -0
- package/dist/organisms/AppShell.svelte.d.ts +157 -0
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarAccountPanel.svelte +107 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
- package/dist/organisms/SidebarNavigation.svelte +111 -43
- package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
- package/dist/organisms/SidebarPanel.svelte +34 -17
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +164 -14
- package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
- package/dist/organisms/TopNavbar.svelte +353 -39
- package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +10 -0
- package/dist/organisms/index.js +7 -0
- package/dist/types/components.d.ts +21 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +1093 -26
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +28 -1
- package/dist/util/app-shell.d.ts +78 -0
- package/dist/util/app-shell.js +97 -0
- package/dist/util/avatar.d.ts +38 -0
- package/dist/util/avatar.js +52 -0
- package/dist/util/bottom-sheet.d.ts +29 -0
- package/dist/util/bottom-sheet.js +43 -0
- package/dist/util/bottom-tab-bar.d.ts +40 -0
- package/dist/util/bottom-tab-bar.js +54 -0
- package/dist/util/calendar.d.ts +55 -0
- package/dist/util/calendar.js +74 -0
- package/dist/util/chart.d.ts +139 -0
- package/dist/util/chart.js +305 -0
- package/dist/util/chip.d.ts +65 -0
- package/dist/util/chip.js +58 -0
- package/dist/util/cn.js +28 -2
- package/dist/util/code-field.d.ts +62 -0
- package/dist/util/code-field.js +112 -0
- package/dist/util/collapsible.d.ts +63 -0
- package/dist/util/collapsible.js +23 -0
- package/dist/util/confirm-dialog.d.ts +11 -0
- package/dist/util/confirm-dialog.js +2 -0
- package/dist/util/date.d.ts +95 -0
- package/dist/util/date.js +237 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/drawer.d.ts +12 -0
- package/dist/util/drawer.js +7 -0
- package/dist/util/dropdown.d.ts +46 -0
- package/dist/util/dropdown.js +21 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +44 -0
- package/dist/util/field.js +38 -0
- package/dist/util/fit-in-viewport.d.ts +200 -0
- package/dist/util/fit-in-viewport.js +399 -0
- package/dist/util/focus-utils.d.ts +77 -2
- package/dist/util/focus-utils.js +186 -9
- package/dist/util/hydration.d.ts +44 -0
- package/dist/util/hydration.js +58 -0
- package/dist/util/keyboard-inset.d.ts +17 -0
- package/dist/util/keyboard-inset.js +53 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/media-grid.d.ts +50 -0
- package/dist/util/media-grid.js +81 -0
- package/dist/util/media.d.ts +5 -0
- package/dist/util/media.js +10 -0
- package/dist/util/overlay-chrome.d.ts +52 -0
- package/dist/util/overlay-chrome.js +56 -0
- package/dist/util/overlay.d.ts +71 -0
- package/dist/util/overlay.js +240 -0
- package/dist/util/photo.d.ts +233 -0
- package/dist/util/photo.js +209 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/pull-to-refresh.d.ts +26 -0
- package/dist/util/pull-to-refresh.js +30 -0
- package/dist/util/radio-keys.d.ts +24 -0
- package/dist/util/radio-keys.js +49 -0
- package/dist/util/rating.d.ts +22 -0
- package/dist/util/rating.js +26 -0
- package/dist/util/ready-made-strings.d.ts +76 -0
- package/dist/util/ready-made-strings.js +67 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/scroll-padding.d.ts +19 -0
- package/dist/util/scroll-padding.js +44 -0
- package/dist/util/segmented-control.d.ts +15 -0
- package/dist/util/segmented-control.js +1 -0
- package/dist/util/select.d.ts +40 -0
- package/dist/util/select.js +63 -0
- package/dist/util/sheet-drag.d.ts +83 -0
- package/dist/util/sheet-drag.js +311 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/skeleton.d.ts +14 -0
- package/dist/util/skeleton.js +22 -0
- package/dist/util/sliding-indicator.d.ts +24 -0
- package/dist/util/sliding-indicator.js +44 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/stepper.d.ts +39 -0
- package/dist/util/stepper.js +38 -0
- package/dist/util/swipeable-list-item.d.ts +35 -0
- package/dist/util/swipeable-list-item.js +32 -0
- package/dist/util/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +132 -0
- package/dist/util/toaster.js +130 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +34 -0
- package/dist/util/top-navbar.js +28 -0
- package/dist/util/touch-target.d.ts +30 -0
- package/dist/util/touch-target.js +30 -0
- package/dist/util/zabi-strings.d.ts +141 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +11 -2
- package/dist/zabi-components-theme-dark-only.css +870 -258
- package/dist/zabi-components-theme-dark.css +870 -258
- package/dist/zabi-components-theme-only.css +2419 -368
- package/dist/zabi-components-theme.css +2419 -368
- package/dist/zabi-components.css +6032 -711
- package/docs/lucide-icons.md +40 -17
- package/docs/theme-imports.md +81 -11
- package/package.json +45 -8
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,2935 @@
|
|
|
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
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **BottomTabBar**: a tab's side padding is 2px (was 4px), so a five-tab bar keeps a label like "Kalender" whole at 320px.
|
|
18
|
+
- **Toaster**: a stack that scrolls keeps the newest toast in view when its words arrive after it is shown and it grows. It was pushed below the end of the stack.
|
|
19
|
+
|
|
20
|
+
## [9.0.0] - 2026-10-13
|
|
21
|
+
|
|
22
|
+
9.0.0 is a new look and a larger set. Bars, sheets, menus and toasts are glass materials, the light page is
|
|
23
|
+
near-white, dark mode is drawn on the neutral ramp, and solid controls have a soft gradient. The shell is
|
|
24
|
+
redesigned: flush bars that turn to glass over content, a large `AppBar` title, an `AppShell` `navigation` slot
|
|
25
|
+
with `AppNavigation`. New components include `Chip` and `ChipGroup`, `Block`, `Stat`, `PickerField`, `CodeField`,
|
|
26
|
+
`SpeechBubble`, the SVG charts (`LineChart`, `BarChart`, `PieChart`, `Sparkline`) and `MapView` (from
|
|
27
|
+
`zabi-components/map`, with `maplibre-gl` as an optional peer). It is a major because of what an app shows
|
|
28
|
+
without a code change, not because of its API: nothing from 8.1.0 was removed or renamed, and every `exports`
|
|
29
|
+
path, prop, variant and token is still there.
|
|
30
|
+
|
|
31
|
+
The work was built as 32 local preview builds, none published to npm. Their sections below are the full record:
|
|
32
|
+
every addition, every changed default and, where one exists, the line that restores the 8.1 look.
|
|
33
|
+
|
|
34
|
+
### Upgrade notes
|
|
35
|
+
|
|
36
|
+
Coming from 8.1.0, these are visible without a code change. The section named in brackets has the detail and
|
|
37
|
+
the line that restores 8.1.
|
|
38
|
+
|
|
39
|
+
**Appearance, light and dark**
|
|
40
|
+
|
|
41
|
+
- The default typeface is the platform's UI face, and transitions use `--ease-standard` (alpha.1).
|
|
42
|
+
- The light page is near-white (`--color-surface-base` is `--zabi-base-50`), a default `Card` has a 1px edge and
|
|
43
|
+
no shadow, status tints are a step lighter and shadows are softer (alpha.1).
|
|
44
|
+
- Primary, accent and danger buttons, the `FloatingActionButton` and the on `Toggle` have a soft gradient. The
|
|
45
|
+
outline button has a 3:1 edge. The off `Toggle` is a light tint, and its knob is 3:1 on the track (alpha.2,
|
|
46
|
+
alpha.26).
|
|
47
|
+
- `SegmentedControl` selects with a neutral thumb, and its indicator and the one in `Tabs` slide. `List` rows
|
|
48
|
+
are divided by a hairline, and a subtle `Badge` has no visible border (alpha.2).
|
|
49
|
+
- `BottomSheet`, `Drawer`, `Modal`, the `Dropdown` menu, the `Select` list, `Tooltip` and every toast are frosted
|
|
50
|
+
glass. `Tooltip` has no arrow by default. `data-materials="opaque"` turns the glass off (alpha.2).
|
|
51
|
+
- `AppBar`, `BottomTabBar`, `StickyActionBar` and `TopNavbar` are the page colour at rest and glass once content
|
|
52
|
+
is under them. `--color-surface-chrome` is the page colour. The active tab has no outline, and the sidebar
|
|
53
|
+
and the top navbar are restyled (alpha.3).
|
|
54
|
+
- Dark surfaces are steps of the neutral ramp, the primary and danger buttons keep their hue in dark under a
|
|
55
|
+
white label (`createTheme({ darkPrimary: 'mirror' })` gives the 8.1 fills), and dark status tints are
|
|
56
|
+
low-chroma (alpha.4).
|
|
57
|
+
- In dark, a form field is a translucent well and a flat `Card` is a translucent layer (alpha.6).
|
|
58
|
+
|
|
59
|
+
**Layout and behaviour**
|
|
60
|
+
|
|
61
|
+
- A `bg-*` or `border-*` class on a bar no longer changes its surface: the fill is painted on the material's
|
|
62
|
+
layer (alpha.3).
|
|
63
|
+
- The `AppShell` footer lies over the scroller, so content passes under the tab bar (alpha.3).
|
|
64
|
+
- `Collapsible` with the default trigger is a native `<details>` with a `<summary>`, not a `<button>`
|
|
65
|
+
(alpha.5).
|
|
66
|
+
- `Input type="password"` has the show/hide button by default (`revealable={false}` removes it), and `Textarea`
|
|
67
|
+
has no resize grip on a touch screen (alpha.6).
|
|
68
|
+
- A `Select` in a sheet with up to six options and no search gets a sheet as tall as its list
|
|
69
|
+
(`sheetFit={false}` restores the half sheet) (alpha.8).
|
|
70
|
+
- A `RadioGroup` option's `description` is no longer part of the radio's name, and `Skeleton` is still under
|
|
71
|
+
reduced motion (alpha.12).
|
|
72
|
+
- `TopNavbar` opens its links in a bottom sheet below `collapseAt` (alpha.16).
|
|
73
|
+
- Toasts: a mouse holds the timer only once it has moved over the toast, a pushed id replaces the showing
|
|
74
|
+
toast in place, and a touch swipe dismisses (alpha.18).
|
|
75
|
+
- `Button` is capped at its container's width and hyphenates a label that does not fit (alpha.32).
|
|
76
|
+
|
|
77
|
+
**Themes**
|
|
78
|
+
|
|
79
|
+
- `createTheme` checks the material pairs it used to skip, so a theme can get a contrast warning it did not
|
|
80
|
+
get in 8.1.0. Links follow an overridden primary (alpha.2, alpha.4).
|
|
81
|
+
- The theme snapshot only gained names: no published token was removed.
|
|
82
|
+
|
|
83
|
+
### Fixed since alpha.32
|
|
84
|
+
|
|
85
|
+
Found by the first full browser run on 9.0.
|
|
86
|
+
|
|
87
|
+
- A secondary `Button` or `IconButton` on a brand block (`on-brand`, `Card tone="brand"`, a brand `AppBar`) in
|
|
88
|
+
dark: its wash was the dark theme's white one, under the block's white label, which left the label at
|
|
89
|
+
4.1:1. On a brand block the wash is now the dark one in both modes (5.7:1). A surface inside the block gets
|
|
90
|
+
the theme's wash back.
|
|
91
|
+
- A brand block inside an accent block, or an accent block inside a brand block, with or without a card
|
|
92
|
+
between them, took its own label colour as its fill: white text on white. The inversion of a solid fill
|
|
93
|
+
inside a block no longer applies to an element that is a block itself.
|
|
94
|
+
|
|
95
|
+
### Not verified
|
|
96
|
+
|
|
97
|
+
The preview builds were tested lightly on purpose (type and design checks, unit tests in jsdom, the package
|
|
98
|
+
build). Not done by anyone: a real phone, Safari, Firefox, a screen reader, an accessibility review. `MapView`
|
|
99
|
+
has never been drawn against a real map: its tests use a stand-in for MapLibre.
|
|
100
|
+
|
|
101
|
+
## [9.0.0-alpha.32] - 2026-10-13
|
|
102
|
+
|
|
103
|
+
A thirty-second local preview build of the 9.0 direction, not published to npm. No token, prop, variant,
|
|
104
|
+
string key, export or data attribute of alpha.31 is removed or renamed. Nothing in it has been rendered in a
|
|
105
|
+
browser: type and design checks, unit tests in jsdom, the package build.
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
|
|
109
|
+
- `MapView`, from a new entry `zabi-components/map`: a map on MapLibre GL JS with markers from a list (Z-137,
|
|
110
|
+
D262). `maplibre-gl` is an optional peer dependency (`>=5.0.0`, built against 6.14.0): the app installs it, and
|
|
111
|
+
the component loads it with `import("maplibre-gl")` when it is mounted in a browser. No other entry of the
|
|
112
|
+
package exports the map, so importing anything else brings none of it. The library's stylesheet is not needed.
|
|
113
|
+
- `points` (`id`, `lng`, `lat`, `label`, `checked`), `styleLight` and `styleDark` (URL or object; the dark one
|
|
114
|
+
is drawn while `data-theme` on `<html>` is dark, or `auto` on a dark system, and a change keeps the position
|
|
115
|
+
and the markers), `onpointpress(point, index)`, `onclusterpress(points)`, `clusterLabel(points)`.
|
|
116
|
+
- Markers are buttons with a 44px tap area in px, in the order of `points`. Markers whose tap areas overlap are
|
|
117
|
+
one count marker, which sits at the place of its first point in the tab order and keeps its element (and the
|
|
118
|
+
focus) while others join or leave it. A marker reached with Tab outside the view is jumped into view.
|
|
119
|
+
- `fitMaxZoom` (default 16), `bind:center`, `bind:zoom`, `onmove`, and the methods `fit()`,
|
|
120
|
+
`showPoint(id, { inset, zoom })` and `showPlace(place, { inset, zoom })` through `bind:this`. Every move is a
|
|
121
|
+
jump. With `center` and `zoom` given at the start the map opens there and is not fitted.
|
|
122
|
+
- `viewer`: a dot at a position the page hands in; the map jumps to it. The component never asks the browser.
|
|
123
|
+
- `bind:status` / `onstatus`: `loading`, `ready`, `library-error`, `tiles-error`. `tiles-error` is told when the
|
|
124
|
+
style cannot be fetched, or tiles failed and none has arrived; a tile that arrives later takes it back.
|
|
125
|
+
- `attribution` (string or snippet); without it the words of the style's own attribution, as plain text. Always
|
|
126
|
+
shown, bottom left. `strings`: `label`, `cluster(count)`, `viewer`. `mapViewSupported()` answers whether the
|
|
127
|
+
browser has WebGL without loading the library.
|
|
128
|
+
- Not in `ZabiStringsProvider` yet: the words come through `strings`. A drag that starts on a marker does not
|
|
129
|
+
pan the map.
|
|
130
|
+
- `Textarea`: `count`, `countFrom` and `countText` show a character count under the field when it has a
|
|
131
|
+
`maxlength`: "12 / 2000" by default, at the end of the line the hint is on, part of the field's description and
|
|
132
|
+
not a live region. `TextareaCount` is exported (Z-125, D254).
|
|
133
|
+
- `Toggle`: `description`, a line under the label and inside it, so a tap on it toggles. It is tied to the switch
|
|
134
|
+
with `aria-describedby`, after any ids passed in, and does not join the name (Z-126, D255).
|
|
135
|
+
- `Checkbox`: `description`, as `Radio` has. `Checkbox` and `Radio`: `controlPosition="end"` puts the control
|
|
136
|
+
last in the row, which then fills its width; the markup order is unchanged (Z-128, D256).
|
|
137
|
+
- `ImageUpload`: `multiple`, `maxFiles` and `onfiles(files)`. With `multiple` the chooser and a drop take several
|
|
138
|
+
files and hand them over together, in order; the component keeps none (no preview, `value` not set,
|
|
139
|
+
`onfileselect` not called). `onfilereject` has a third reason, `"count"`, only with `maxFiles` (Z-130, D258).
|
|
140
|
+
- `PhotoViewer`: `onloaderror(photo, index)`, once per address that fails, and `onretry(photo, index)`, called
|
|
141
|
+
when "Try again" is pressed; a promise it returns is waited for, with the button busy, before the image is
|
|
142
|
+
asked for again. `PhotoGrid`: `onloaderror(photo, index)` for a thumbnail (Z-132, D259).
|
|
143
|
+
- `PhotoGrid`: a photo's `status` (`uploading`, with an optional `progress` from 0 to 1, or `error`) is drawn on
|
|
144
|
+
its tile and said in its name; `onretry(photo, index)` puts a "Try again" button on a failed one. New strings
|
|
145
|
+
with defaults: `uploading`, `uploadFailed`, `retryUpload(name)`. `PhotoStatus` is exported (Z-133, D260).
|
|
146
|
+
- `PhotoViewer`: a photo's `credit` is a second, quieter line under the caption, outside what "More" folds. The
|
|
147
|
+
dialog is described by the caption and the credit after the photo's name and place (Z-135, D261).
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
|
|
151
|
+
- `Button`: never wider than its container (`max-inline-size: 100%`, as `Badge`), and a word that alone does not fit is hyphenated by
|
|
152
|
+
the page's language (`hyphens-auto`). A label that already wraps may now be hyphenated at a line's end
|
|
153
|
+
(Z-129, D257).
|
|
154
|
+
- `Checkbox`, `Radio`, `RadioGroup`: the input's touch target is 44px in px, centred on the box. In rem it grew
|
|
155
|
+
with the text: 88px at 200%, past the screen's edge from a box at the end of a row (Z-128, D256).
|
|
156
|
+
- `PhotoGrid`: nothing leaves the add tile at large text. The label is cut at two lines (the name keeps the whole
|
|
157
|
+
text) and the plus is drawn only where the tile has room for it above two lines (Z-136, D260).
|
|
158
|
+
- `PhotoViewer`: when the control that had the focus is taken out (an action that goes with its photo, with or
|
|
159
|
+
without a confirm dialog between) and the focus is left on `<body>`, Close takes it (Z-134, D261).
|
|
160
|
+
|
|
161
|
+
### Notes
|
|
162
|
+
|
|
163
|
+
- `Collapsible` is not for a "show all" under a list; a `Button` with `aria-expanded` and `aria-controls` under
|
|
164
|
+
the list is the intended way (Z-127, D263).
|
|
165
|
+
- `PhotoViewer` does not take the browser's Back: a history entry made by a component conflicts with the app's
|
|
166
|
+
router. Open it from the router's own state, as SvelteKit's shallow routing does (Z-131, D263).
|
|
167
|
+
|
|
168
|
+
## [9.0.0-alpha.31] - 2026-10-11
|
|
169
|
+
|
|
170
|
+
A thirty-first local preview build of the 9.0 direction, not published to npm. No token, prop, variant,
|
|
171
|
+
string key, export or data attribute of alpha.30 is removed or renamed.
|
|
172
|
+
|
|
173
|
+
### Fixed
|
|
174
|
+
|
|
175
|
+
- `Input`: a placeholder longer than the field ends in an ellipsis (`placeholder-shown:text-ellipsis`), not half way
|
|
176
|
+
through a letter. A typed value is unchanged: it still scrolls with the caret. A placeholder that fits looks the
|
|
177
|
+
same. Fields built on `Input` (`ColorPicker`, the search field of `Select`, `SidebarPanel` and
|
|
178
|
+
`SidebarNavigation`) get it too (Z-124, D252).
|
|
179
|
+
|
|
180
|
+
## [9.0.0-alpha.30] - 2026-10-11
|
|
181
|
+
|
|
182
|
+
A thirtieth local preview build of the 9.0 direction, not published to npm. No token, prop, variant,
|
|
183
|
+
string key, export or data attribute of alpha.29 is removed or renamed.
|
|
184
|
+
|
|
185
|
+
### Added
|
|
186
|
+
|
|
187
|
+
- `Skeleton`: every piece carries `data-skeleton` (its variant; a line of a `lines` paragraph is `text`, beside the
|
|
188
|
+
existing `data-skeleton-line`), in every branch: announced, `decorative`, and inside a `SkeletonGroup`. A
|
|
189
|
+
paragraph's wrapper has `data-skeleton-paragraph`. The `animate-pulse` class stays on the pieces (Z-123, D251).
|
|
190
|
+
- `Skeleton` and `SkeletonGroup` pass other `div` attributes (`id`, `data-testid`, `style`, `aria-*`) to their
|
|
191
|
+
root element. `role`, `aria-busy`, `aria-hidden` and `data-skeleton` are not overridden. `SkeletonGroup` has
|
|
192
|
+
`data-skeleton-group`.
|
|
193
|
+
|
|
194
|
+
## [9.0.0-alpha.29] - 2026-10-10
|
|
195
|
+
|
|
196
|
+
A twenty-ninth local preview build of the 9.0 direction, not published to npm. No token, prop, variant,
|
|
197
|
+
string key, export or data attribute of alpha.28 is removed or renamed.
|
|
198
|
+
|
|
199
|
+
### Fixed
|
|
200
|
+
|
|
201
|
+
- `Select`: an option in the list that opens has the pointer cursor, as a `DropdownItem` has; a disabled option
|
|
202
|
+
keeps `not-allowed`.
|
|
203
|
+
- `ActionPanel` as a button (`onclick`, no `href`) has the pointer cursor.
|
|
204
|
+
|
|
205
|
+
## [9.0.0-alpha.28] - 2026-10-10
|
|
206
|
+
|
|
207
|
+
A twenty-eighth local preview build of the 9.0 direction, not published to npm. No token, prop, variant,
|
|
208
|
+
string key, export or data attribute of alpha.27 is removed or renamed.
|
|
209
|
+
|
|
210
|
+
### Added
|
|
211
|
+
|
|
212
|
+
- `AppBar`: the row carries `data-title-empty` when the bar has no `title`.
|
|
213
|
+
|
|
214
|
+
### Fixed
|
|
215
|
+
|
|
216
|
+
- `AppBar` with no `title` and a wide `leading`: the empty title part no longer asks for 72px, so it no longer
|
|
217
|
+
sends the bar to `data-title-row="own"` and takes a row of its own (the bar was 12px taller). A bar with
|
|
218
|
+
nothing to show as a title stays on one row whatever room is left, on the server and before it is measured
|
|
219
|
+
as well; `leading` is still cut to the room beside the back control and the actions. A bar with a title is
|
|
220
|
+
unchanged.
|
|
221
|
+
|
|
222
|
+
## [9.0.0-alpha.27] - 2026-10-10
|
|
223
|
+
|
|
224
|
+
A twenty-seventh local preview build of the 9.0 direction, not published to npm. Loose ends. No token,
|
|
225
|
+
prop, variant, string key, export or data attribute of alpha.26 is removed or renamed.
|
|
226
|
+
|
|
227
|
+
### Added
|
|
228
|
+
|
|
229
|
+
- `zabi-components/atoms` now also exports `Toast`, `Tooltip`, `Progress`, `Skeleton`, `SkeletonGroup`,
|
|
230
|
+
`CodeBlock`, `FeatureCard`, `OptimizedImage`, `DEFAULT_SKELETON_STRINGS` and the type `SkeletonStrings`,
|
|
231
|
+
which the root entry already had.
|
|
232
|
+
- Bottom sheets (`BottomSheet`, `SlideUp`): content with `touch-action: none`, or `pan-x` alone, under the
|
|
233
|
+
finger keeps its own gesture (a map, a canvas, a drawing pad); no `data-sheet-no-drag` is needed.
|
|
234
|
+
`data-sheet-no-drag` still works.
|
|
235
|
+
|
|
236
|
+
### Changed
|
|
237
|
+
|
|
238
|
+
- Bottom sheets: the axis of a swipe in the content is decided once per gesture, after 8px. A swipe that
|
|
239
|
+
begins sideways stays sideways and never moves the sheet, even if it later turns down. A downward swipe
|
|
240
|
+
of under 8px no longer starts a drag. The grip is unchanged.
|
|
241
|
+
|
|
242
|
+
### Fixed
|
|
243
|
+
|
|
244
|
+
- `Drawer`: the overlay root is `overflow-clip`, not `overflow-hidden`, as the bottom sheet's already was
|
|
245
|
+
(WebKit scrolled a hidden root to a focused control while the panel was still off screen).
|
|
246
|
+
|
|
247
|
+
## [9.0.0-alpha.26] - 2026-10-10
|
|
248
|
+
|
|
249
|
+
A twenty-sixth local preview build of the 9.0 direction, not published to npm. Three rulings from the
|
|
250
|
+
owner (2026-10-10). No token, prop, variant, string key, export or data attribute of alpha.25 is removed or
|
|
251
|
+
renamed. One default colour changes on purpose: the `Toggle` knob (below). Run on this build: the type and
|
|
252
|
+
design checks, the unit suite, the package build and a scratch install; no browser.
|
|
253
|
+
|
|
254
|
+
### Changed
|
|
255
|
+
|
|
256
|
+
- `Toggle`: the default knob is now 3:1 or more on its track in every enabled state, light and dark
|
|
257
|
+
(WCAG 1.4.11). Before, the knob was the card colour: 1.27:1 on the off track in light and 1.57:1 in dark, and
|
|
258
|
+
1.74:1 to 3.39:1 on the primary fill in dark. Now off is `--color-toggle-knob` (light base-500: 3.81:1 on the resting
|
|
259
|
+
track, 3.53:1 hovered, 3.27:1 pressed; dark base-700: 7.07:1, 6.13:1, 5.23:1) and on is
|
|
260
|
+
`--color-toggle-knob-checked` (the primary fill's label colour: 4.86:1, 6.76:1, 9.44:1 on the fill at rest,
|
|
261
|
+
hovered, pressed). The off track's edge is unchanged (4.63:1 on the page and 4.83:1 on a card in light).
|
|
262
|
+
`--color-toggle-thumb` still wins when set; `--color-toggle-thumb-checked`, declared nowhere, sets the
|
|
263
|
+
on knob alone. The knob pairs are in the contrast guard. (Z-121, D240)
|
|
264
|
+
|
|
265
|
+
### Fixed
|
|
266
|
+
|
|
267
|
+
- `Checkbox`, `Radio`: the box is centred on the row again and the row keeps its height and touch target, as
|
|
268
|
+
before alpha.25; the first-line alignment and `pointer-coarse:py-3` of alpha.25 are withdrawn. The box
|
|
269
|
+
still does not shrink (`shrink-0`). (Z-119, D241)
|
|
270
|
+
- Forced colours: a busy button `Card` (`loading`) has `GrayText` text and a dashed `GrayText` edge, and the
|
|
271
|
+
`Toaster` time bar's track has a `CanvasText` edge. Nothing changes outside `forced-colors: active`.
|
|
272
|
+
(D242)
|
|
273
|
+
|
|
274
|
+
## [9.0.0-alpha.25] - 2026-10-10
|
|
275
|
+
|
|
276
|
+
A twenty-fifth local preview build of the 9.0 direction, not published to npm. Five entries
|
|
277
|
+
from the app team (Z-117 to Z-121). No token, prop, variant, string key, export or data attribute of
|
|
278
|
+
alpha.24 is removed or renamed; new props, tokens and string keys are optional. Run on this build: the
|
|
279
|
+
type and design checks, the unit suite, the package build and a scratch install; no browser.
|
|
280
|
+
|
|
281
|
+
### Added
|
|
282
|
+
|
|
283
|
+
- `TimeField` `openAt` (`HH:mm`): where an empty field's columns stand when the sheet opens. Nothing is
|
|
284
|
+
chosen and `value` stays empty. Without it the columns stand at now, on the minute step. (Z-117, D235)
|
|
285
|
+
- `TimeField` string key `chosenTime` ("Chosen time"): the sheet says the time Done will set, beside
|
|
286
|
+
Done, so it is on the screen even when a column is scrolled away from it (Z-118, D236).
|
|
287
|
+
- `--color-toggle-thumb`, declared nowhere: the `Toggle` knob's colour. Unset, the knob is the card colour as
|
|
288
|
+
before (Z-121, D239).
|
|
289
|
+
|
|
290
|
+
### Fixed
|
|
291
|
+
|
|
292
|
+
- `Checkbox`, `Radio`: the box no longer shrinks when the label wraps (`shrink-0` always), and sits by the
|
|
293
|
+
first line of a label that wraps (Z-119, D237).
|
|
294
|
+
- `AppBar`: the parts of the row are measured as laid out (up to the next pixel) and not by `offsetWidth`,
|
|
295
|
+
so actions with a fractional width no longer wrap to a row of their own (Z-120, D238).
|
|
296
|
+
|
|
297
|
+
## [9.0.0-alpha.24] - 2026-10-10
|
|
298
|
+
|
|
299
|
+
A twenty-fourth local preview build of the 9.0 direction, not published to npm. Two entries
|
|
300
|
+
from the app team (Z-115, Z-116). No token, prop, variant, string key, export or data attribute of
|
|
301
|
+
alpha.23 is removed or renamed; nothing changes outside `forced-colors: active`. Run on this
|
|
302
|
+
build: the type and design checks, the unit suite, the package build and a scratch install; no browser.
|
|
303
|
+
|
|
304
|
+
### Fixed
|
|
305
|
+
|
|
306
|
+
- Forced colours (Windows contrast themes): `Toggle` has a 1px `ButtonText` edge on the track, a thumb
|
|
307
|
+
in `ButtonText`; on is a `Highlight` track with a `HighlightText` thumb; disabled is `GrayText`
|
|
308
|
+
(Z-115, D232).
|
|
309
|
+
- Forced colours: a ticked `Checkbox`, `Radio` and `RadioGroup` option is a `Highlight` box with a
|
|
310
|
+
`HighlightText` tick or dot, an unticked one an edge in `ButtonText`, a disabled one `GrayText`
|
|
311
|
+
(Z-116, D233).
|
|
312
|
+
- Forced colours: `Slider` has a `Highlight` fill on a `Canvas` track with a `ButtonText` edge, and the
|
|
313
|
+
`Toaster` time bar is `Highlight` (D234).
|
|
314
|
+
|
|
315
|
+
## [9.0.0-alpha.23] - 2026-10-09
|
|
316
|
+
|
|
317
|
+
A twenty-third local preview build of the 9.0 direction, not published to npm. One entry
|
|
318
|
+
from the app team (Z-114). No token, prop, variant, string key, export or data attribute of
|
|
319
|
+
alpha.22 is removed or renamed. Run on this build: the type and design checks, the unit
|
|
320
|
+
suite, the package build and a scratch install; no browser.
|
|
321
|
+
|
|
322
|
+
### Fixed
|
|
323
|
+
|
|
324
|
+
- `Checkbox`, `Radio` and `RadioGroup`: the edge of an unticked box is `--color-control-border` (3:1 on
|
|
325
|
+
every surface, both themes) and not the fixed step `base-400`, which was 2.5:1 in light and 1.9 to 2.3:1
|
|
326
|
+
in dark. In a brand or accent block it follows the block's on-colour too (Z-114, D231).
|
|
327
|
+
|
|
328
|
+
## [9.0.0-alpha.22] - 2026-10-09
|
|
329
|
+
|
|
330
|
+
A twenty-second local preview build of the 9.0 direction, not published to npm. Six entries
|
|
331
|
+
from the app team (Z-108 to Z-113). No token, prop, variant, string key, export or data attribute of
|
|
332
|
+
alpha.21 is removed or renamed. Run on this build: the type and design checks, the unit
|
|
333
|
+
suite, the package build and a scratch install; no browser.
|
|
334
|
+
|
|
335
|
+
### Added
|
|
336
|
+
|
|
337
|
+
- `Card` `loading` (with `type`): `aria-busy` and `aria-disabled`, the press swallowed, the pressed look held,
|
|
338
|
+
size and Tab stop kept (Z-108, D225).
|
|
339
|
+
- `BottomTabBar` `pending`: the tab just pressed takes the mark while `aria-current="page"` stays on `active`
|
|
340
|
+
until it changes; the pending link has `aria-busy` (Z-109, D226).
|
|
341
|
+
- `.z-fixed`, beside `.z-sticky` and the other layer classes (Z-113, D230).
|
|
342
|
+
|
|
343
|
+
### Fixed
|
|
344
|
+
|
|
345
|
+
- `Button` in forced colours: the variants drawn by a fill (`primary`, `accent`, `secondary`, `danger`,
|
|
346
|
+
`tonal`) get a 1px `ButtonBorder` edge there, and a disabled or loading button a `GrayText` one (Z-110, D227).
|
|
347
|
+
- `Stepper` inside a `SkeletonGroup` no longer renders its own `role="status"` (Z-111, D228).
|
|
348
|
+
- `Skeleton`: the variant's width and height are written at zero specificity (`:where()`), so any size
|
|
349
|
+
class at the call site wins, including a size name of the app's own (Z-112, D229).
|
|
350
|
+
|
|
351
|
+
## [9.0.0-alpha.21] - 2026-10-08
|
|
352
|
+
|
|
353
|
+
A twenty-first local preview build of the 9.0 direction, not published to npm. One entry
|
|
354
|
+
from the app team (Z-107). No token, prop, variant, string key, export or data attribute of
|
|
355
|
+
alpha.20 is removed or renamed. Run on this build: the type and design checks, the unit
|
|
356
|
+
suite, the package build and a scratch install; no browser.
|
|
357
|
+
|
|
358
|
+
### Fixed
|
|
359
|
+
|
|
360
|
+
- `BottomSheet`, `SlideUp` (and so `Select`, `Dropdown` and `TopNavbar` sheets, `TimeField`, `DateField`): a
|
|
361
|
+
swipe down that starts on a box inside the content that scrolls by itself (the hours and minutes of
|
|
362
|
+
`TimeField`, the list of a `Select`, a long text area) no longer moves the sheet; the box keeps the
|
|
363
|
+
gesture, at its top too, and nothing is handed over mid-gesture (Z-107, D224). The sheet still follows
|
|
364
|
+
a swipe from the grip, the header, the content outside such a box when the content is at its top.
|
|
365
|
+
Default change: a swipe down on a scrolling box at its top used to close the sheet and now does not.
|
|
366
|
+
|
|
367
|
+
### Added
|
|
368
|
+
|
|
369
|
+
- `data-sheet-no-drag` is read for a touch in the content as well as in the header: anything carrying
|
|
370
|
+
it, or inside it, and every `<input type="range">`, keeps its drag from the sheet. `TimeField`'s
|
|
371
|
+
columns, `ColorPicker`'s colour map and the handle of `SortableList` carry it.
|
|
372
|
+
|
|
373
|
+
## [9.0.0-alpha.20] - 2026-10-08
|
|
374
|
+
|
|
375
|
+
A twentieth local preview build of the 9.0 direction, not published to npm. Two
|
|
376
|
+
entries from the app team, the first time the snackbar was drawn in a browser
|
|
377
|
+
(Z-105, Z-106). No token, prop, variant, string key or export of alpha.19 is removed
|
|
378
|
+
or renamed. Run on this build: the type and design checks, the unit suite, the package
|
|
379
|
+
build and a scratch install; no browser.
|
|
380
|
+
### Added
|
|
381
|
+
|
|
382
|
+
- `Toaster` `timeLine?: boolean` and `pushToast` / `ToastItem` `timeLine?: boolean`: whether the thin
|
|
383
|
+
bar along the bottom of a timed toast is drawn. Unset, it is drawn as before, except under a
|
|
384
|
+
confirmation in the snackbar that has nothing to press (timed, no action, `closable: false`),
|
|
385
|
+
where it is now left out. `false` leaves it out, `true` draws it; a toast's own value wins over the
|
|
386
|
+
Toaster's. The bar has `data-toast-timeline`; the sentence for a screen reader about the time left stays.
|
|
387
|
+
- A unit test that fails on a Tailwind class put together at run time (`max-sm:${x}`, `w-[${px}px]`).
|
|
388
|
+
### Changed
|
|
389
|
+
|
|
390
|
+
- `Toaster mode="auto"`: the clip that makes the toast come out from behind the docked edge was a
|
|
391
|
+
class assembled at run time (`max-sm:` + the rule), which is in no stylesheet, so below 640px the
|
|
392
|
+
toast slid in over the tab bar. It is written out whole now. Nothing to change in an app; an app
|
|
393
|
+
that carried the class itself as a stand-in can remove it.
|
|
394
|
+
- A snackbar confirmation with nothing to press no longer has the time bar (see Added); pass
|
|
395
|
+
`timeLine` to bring it back.
|
|
396
|
+
|
|
397
|
+
## [9.0.0-alpha.19] - 2026-10-08
|
|
398
|
+
|
|
399
|
+
A nineteenth local preview build of the 9.0 direction, not published to npm. One
|
|
400
|
+
entry from the app team (Z-104), about `CodeField`; no token, prop, variant,
|
|
401
|
+
string key or export of alpha.18 is removed or renamed. One default changes where
|
|
402
|
+
it was a defect, under Changed. Run on this build: the type and design checks,
|
|
403
|
+
the unit suite, the package build and a scratch install; no browser.
|
|
404
|
+
### Added
|
|
405
|
+
|
|
406
|
+
- `CodeField` `groups?: number | number[]`: draws the boxes in groups with a wider gap
|
|
407
|
+
(`groups={4}` on 8 places is 4 and 4, `groups={[3, 3, 4]}` is explicit). Only the drawing:
|
|
408
|
+
one input, one value, `one-time-code`, the post and a paste of "1234 5678" or "1234-5678" are
|
|
409
|
+
unchanged, and a screen reader still hears one field. A row that cannot fit wraps between groups.
|
|
410
|
+
- `CodeField` `size?: "sm" | "md" | "lg"` (default `"md"`, today's look): `sm` is a compact row, 44px tall.
|
|
411
|
+
- Export of the type `CodeFieldSize`.
|
|
412
|
+
|
|
413
|
+
### Changed
|
|
414
|
+
|
|
415
|
+
- `CodeField` boxes no longer shrink below 1.15em of their own digit, so a digit is never wider than
|
|
416
|
+
its box at large text or a narrow screen (it overlapped the box edges at 200% text with eight
|
|
417
|
+
places); where the boxes would have been narrower the row now wraps (between groups when `groups`
|
|
418
|
+
is set). At 100% text nothing changes up to 9 places at 320px (10 at 360px); a longer code that is not grouped now wraps there instead of drawing slivers. A tap in the gap between boxes or on a second row now
|
|
419
|
+
puts the caret at the nearest box.
|
|
420
|
+
|
|
421
|
+
## [9.0.0-alpha.18] - 2026-10-08
|
|
422
|
+
|
|
423
|
+
An eighteenth local preview build of the 9.0 direction, not published to npm. It
|
|
424
|
+
makes the toast work on a phone (the app team's Z-097 to Z-103). No token, prop,
|
|
425
|
+
variant, string key or export of alpha.17 is removed or renamed, and a `Toaster`
|
|
426
|
+
with no new prop looks and behaves as before, except for the four defect fixes
|
|
427
|
+
under Changed. Run on this build: the type and design checks, the unit suite,
|
|
428
|
+
the package build and a scratch install; no browser, no screen reader.
|
|
429
|
+
### Added
|
|
430
|
+
|
|
431
|
+
- `Toaster` `mode?: "stack" | "snackbar" | "auto"` (default `"stack"`, type `ToasterMode`): the
|
|
432
|
+
snackbar is one row of 52px (two lines at most), one at a time, its action beside its text, set
|
|
433
|
+
directly above what is docked at the bottom and sliding out from behind that edge on
|
|
434
|
+
`--ease-standard` (no overshoot, no movement under reduced motion). `"auto"` is the snackbar below
|
|
435
|
+
640px and the stack from there up.
|
|
436
|
+
- `Toaster` `max` (how many show; the oldest timed toast leaves, never an error, one with an action or
|
|
437
|
+
a persistent one; 1 in the snackbar), `avoid` (a CSS selector for what the stack must not lie on)
|
|
438
|
+
and `swipeToDismiss` (default `true`).
|
|
439
|
+
- The toaster finds a `BottomTabBar` or `StickyActionBar` (`data-bar-edge="bottom"`), a
|
|
440
|
+
`FloatingActionButton` and a dirty `UnsavedChangesBar` (new `data-toast-clear`) that lie under the
|
|
441
|
+
stack and sits above the highest, at any width.
|
|
442
|
+
- `pushToast` and `ToastItem`: `closable?: boolean` (default `true`); `false` leaves the close button
|
|
443
|
+
out of a short timed toast, which then has no control and takes no taps. `pushToast` takes `id?`.
|
|
444
|
+
### Changed
|
|
445
|
+
|
|
446
|
+
- A mouse holds a toast's timer once it has moved over the toast, not when the toast arrives under a
|
|
447
|
+
pointer at rest (Z-098: it never closed). `mouseenter` and `pointerenter` alone no longer hold it.
|
|
448
|
+
- A toast pushed with the id of one that is showing replaces it in place and restarts its time
|
|
449
|
+
(before, a second item with the same id was added, which a keyed list cannot have).
|
|
450
|
+
- A touch or a pen swipe dismisses a toast (sideways; `swipeToDismiss={false}` turns it off). The close
|
|
451
|
+
button stays. The toast has `touch-action: pan-y`.
|
|
452
|
+
- One line of text is centred on the buttons beside it; text that wraps stays at the top.
|
|
453
|
+
- The words of a toast are put in its `role="status"` / `role="alert"` element about 100 ms after the
|
|
454
|
+
toast is mounted, so the live region is in the page empty first (Z-102). The server's markup has
|
|
455
|
+
them at once. Tests that read a toast's text right after `pushToast` need to wait for it.
|
|
456
|
+
|
|
457
|
+
## [9.0.0-alpha.17] - 2026-10-08
|
|
458
|
+
|
|
459
|
+
A seventeenth local preview build of the 9.0 direction, not published to npm. It
|
|
460
|
+
finishes the `TopNavbar` sheet of alpha.16; no token, prop, variant or export of
|
|
461
|
+
alpha.16 is removed or renamed, and no default changes. Run on this build: the
|
|
462
|
+
type and design checks, the unit suite, the package build and a scratch install; no
|
|
463
|
+
browser.
|
|
464
|
+
### Changed
|
|
465
|
+
|
|
466
|
+
- `TopNavbarStrings`: `menuTitle` (alpha.16) and the new `themeHeading` are optional in the
|
|
467
|
+
interface, with their defaults in `DEFAULT_TOP_NAVBAR_STRINGS` (now `Required<TopNavbarStrings>`).
|
|
468
|
+
Code that writes out a complete `TopNavbarStrings` object, as it could before alpha.16, type-checks
|
|
469
|
+
again; this repairs the note on alpha.16 that said it did not.
|
|
470
|
+
- `TopNavbar` sheet: it grows with its content up to the full screen below the status bar, where
|
|
471
|
+
alpha.16 stopped at half of it, so eight links and the two sections below them do not scroll in
|
|
472
|
+
a short box. Choosing a theme or an accent does not close the sheet.
|
|
473
|
+
### Added
|
|
474
|
+
|
|
475
|
+
- `BottomSheet` `fitMax?: "half" | "full"` (default `"half"`): with `fit`, how tall the sheet may
|
|
476
|
+
grow before its content scrolls. Without it nothing changes.
|
|
477
|
+
|
|
478
|
+
## [9.0.0-alpha.16] - 2026-10-08
|
|
479
|
+
|
|
480
|
+
A sixteenth local preview build of the 9.0 direction, not published to npm. One
|
|
481
|
+
behaviour change in `TopNavbar`, which you can turn off with one attribute; no
|
|
482
|
+
token, prop, variant or export of alpha.15 is removed or renamed. Run on this
|
|
483
|
+
build: the type and design checks, the unit suite, the package build and a
|
|
484
|
+
scratch install; no browser.
|
|
485
|
+
### Changed
|
|
486
|
+
|
|
487
|
+
- `TopNavbar`: below `collapseAt` the menu button opens the links in a bottom sheet (`BottomSheet`,
|
|
488
|
+
`fit`) instead of the panel under the bar. The sheet is a modal dialog: focus moves in and back
|
|
489
|
+
to the button, Escape, the backdrop, the close button and a swipe down close it, the page behind
|
|
490
|
+
is locked, it is named by the new string `menuTitle` ("Menu"), and its links sit in a `nav` named
|
|
491
|
+
by `ariaLabel` (else `menuTitle`). Rows are full width and 44px; `actions`, the theme toggle, the
|
|
492
|
+
current-page mark and the new-tab note are as in the panel; a followed link, a `currentPath`
|
|
493
|
+
change and a wider screen close it. The menu button gets `aria-haspopup="dialog"`, and the bar no
|
|
494
|
+
longer takes the glass while the menu is open. **Opt out:** `menu="inline"` is the panel of
|
|
495
|
+
alpha.15 and earlier, unchanged. Without script the phone menu is as inert as before.
|
|
496
|
+
- `TopNavbar` sheet, below the links: the `actions` and the theme choice have a section of their
|
|
497
|
+
own at the full width of the sheet. The built-in theme toggle is a segmented control there
|
|
498
|
+
(light / dark, or system / light / dark with `themeModes="three"`, 44px on a touch screen, words
|
|
499
|
+
from `themeLabels`, heading from the new string `themeHeading`, "Theme"); it writes the page
|
|
500
|
+
as `ThemeToggle` does, which stays as it was in the bar. Actions are laid out in a column at
|
|
501
|
+
full width. The docs site's `BrandSwitcher` is a labelled group of large rows in the sheet and
|
|
502
|
+
the small button in the bar.
|
|
503
|
+
- `BottomSheet`: the slide-in is on the standard curve (`--ease-standard`) with no overshoot;
|
|
504
|
+
snapping between snap points and settling a drag keep `--ease-spring`.
|
|
505
|
+
### Added
|
|
506
|
+
|
|
507
|
+
- `getTopNavbarPlace()` and type `TopNavbarPlace` (`"bar" | "menu"`): a control in `TopNavbar`'s
|
|
508
|
+
`actions` calls it while it initialises to know whether it is in the bar or the sheet; the
|
|
509
|
+
snippet gets no argument. `"bar"` outside a bar and in `menu="inline"`.
|
|
510
|
+
- `TopNavbar` `menu?: "sheet" | "inline"` (default `"sheet"`), exported type `TopNavbarMenu`, strings
|
|
511
|
+
`menuTitle` and `themeHeading` in `TopNavbarStrings` (code that writes out a complete
|
|
512
|
+
`TopNavbarStrings` object needs the new keys; `strings` itself is partial). Stories for both modes, catalog entry, docs, `?menu=inline`
|
|
513
|
+
on the chaos-lab page.
|
|
514
|
+
|
|
515
|
+
## [9.0.0-alpha.15] - 2026-10-08
|
|
516
|
+
|
|
517
|
+
A fifteenth local preview build of the 9.0 direction, not published to npm. One
|
|
518
|
+
addition to `AppShell`, `AppShellHeaderPart`; no token, prop, variant or export of
|
|
519
|
+
alpha.14 is removed or renamed, and no default changes. Run on this build: the type
|
|
520
|
+
and design checks, the unit suite, the package build and a scratch install; no
|
|
521
|
+
browser.
|
|
522
|
+
### Added
|
|
523
|
+
|
|
524
|
+
- `AppShellHeaderPart`: the consumer chooses what stays at the top. Put several in `AppShell`'s
|
|
525
|
+
`header` snippet, each with `sticky` (default `true`) or `sticky={false}` to scroll away with
|
|
526
|
+
the content: the bar can leave while a filter row stays, or a large title can go while the bar
|
|
527
|
+
and the tabs stay. Plain `position: sticky` in the scroller's flow, no scroll listener; the
|
|
528
|
+
stuck parts stack under one another in document order (a part that scrolls away in between
|
|
529
|
+
does not leave a gap), and the shell reports only what is stuck as `--app-shell-top-inset` and
|
|
530
|
+
`scroll-padding-top`. One glass surface and one hairline under the lowest stuck part; the
|
|
531
|
+
status bar is covered by whatever is topmost, also after the first part has gone. Stuck parts
|
|
532
|
+
may cover 40% of the scroller at most, the lower ones then scroll (`data-released`). A header
|
|
533
|
+
without parts and `AppBar collapseOnScroll` / `largeTitle` behave as before. `AppBar` now
|
|
534
|
+
carries `data-appbar`. Types: `AppShellHeaderPartProps`. Story, catalog entry and demo toggle.
|
|
535
|
+
|
|
536
|
+
## [9.0.0-alpha.14] - 2026-10-08
|
|
537
|
+
|
|
538
|
+
A fourteenth local preview build of the 9.0 direction, not published to npm. One
|
|
539
|
+
entry from the app team (Z-096), a new component `CodeField`; no token, prop,
|
|
540
|
+
variant or export of alpha.13 is removed or renamed, and no default changes. Run
|
|
541
|
+
on this build: the type and design checks, the unit suite, the package build and
|
|
542
|
+
a scratch install; no browser.
|
|
543
|
+
### Added
|
|
544
|
+
|
|
545
|
+
- `CodeField`: a field for a verification or one-time code (Z-096). One real `<input>`
|
|
546
|
+
(`autocomplete="one-time-code"`, `inputmode="numeric"`) under boxes drawn and hidden from
|
|
547
|
+
assistive technology, so the iPhone and Android code suggestion, paste of a whole code
|
|
548
|
+
("123 456" and "123-456" too, with the caret anywhere) and one accessible name work, and the
|
|
549
|
+
form posts the code by `name` with scripts off, where the input is a plain bordered field.
|
|
550
|
+
`length` (1 to 12, default 6), `value` (bindable), `mode` (`numeric` default, `alphanumeric`),
|
|
551
|
+
`label` / `labelHidden`, `hint`, `error`, `variant` + `message`, `required`, `disabled`,
|
|
552
|
+
`readonly`, `oncomplete(value)` and `strings`; the words (`codeField.label`,
|
|
553
|
+
`codeField.description`) come from `ZabiStringsProvider`. Exports: `CodeField`,
|
|
554
|
+
`DEFAULT_CODE_FIELD_STRINGS`, types `CodeFieldProps`, `CodeFieldMode`, `CodeFieldStrings`.
|
|
555
|
+
|
|
556
|
+
## [9.0.0-alpha.13] - 2026-10-07
|
|
557
|
+
|
|
558
|
+
A thirteenth local preview build of the 9.0 direction, not published to npm. Two
|
|
559
|
+
entries from the app team (Z-094, Z-095), both about `Skeleton`; no token, prop,
|
|
560
|
+
variant or export of alpha.12 is removed or renamed, and no default changes: a
|
|
561
|
+
lone `Skeleton` is the same status named "Loading…". Run on this build: the type
|
|
562
|
+
and design checks, the unit suite, the package build and a scratch install; no
|
|
563
|
+
browser.
|
|
564
|
+
### Added
|
|
565
|
+
|
|
566
|
+
- `SkeletonGroup`: one `role="status"` with the loading label for the `Skeleton` pieces
|
|
567
|
+
inside it, which are hidden from assistive tech, so a screen of eight pieces is announced
|
|
568
|
+
once. `Skeleton` takes `decorative` (no role, no label, `aria-hidden`) for a piece beside
|
|
569
|
+
a status of your own, and `strings` (`label`); the label reads the provider's
|
|
570
|
+
`skeleton.label` before the English "Loading…", and `aria-label` still wins. A lone
|
|
571
|
+
`Skeleton` is unchanged. (Z-094)
|
|
572
|
+
- `Skeleton` takes `tone="on-color"`: the surface's label colour at 24% (`bg-skeleton-on-color`,
|
|
573
|
+
in `@layer utilities`), for the `on-brand`, `on-accent` and `on-fill` blocks and a `Card` with
|
|
574
|
+
`tone`, in light and dark; `--zabi-skeleton-fill` replaces it. The default `tone="neutral"` is
|
|
575
|
+
`bg-neutral-subtle` as before. And `lines` (1 to 20, text variant): one placeholder with that many
|
|
576
|
+
lines and a shorter last line (60%); `width` and `class` apply to the paragraph, `height` to each
|
|
577
|
+
line, and it is one status like a lone piece. (Z-095)
|
|
578
|
+
|
|
579
|
+
## [9.0.0-alpha.12] - 2026-10-07
|
|
580
|
+
|
|
581
|
+
A twelfth local preview build of the 9.0 direction, not published to npm. Five
|
|
582
|
+
entries from the app team (Z-089 to Z-093); no token, prop, variant or export of
|
|
583
|
+
alpha.11 is removed or renamed. Two defaults change what a consumer gets: a
|
|
584
|
+
`RadioGroup` option with a description is named by its label alone, and a
|
|
585
|
+
one-line option is centred in its row. Run on this build: the type and design
|
|
586
|
+
checks, the unit suite, the package build and a scratch install; no browser.
|
|
587
|
+
|
|
588
|
+
### Added
|
|
589
|
+
|
|
590
|
+
- `Chip` takes `loading` on its button forms (`type="submit"`, `"button"` and the toggle):
|
|
591
|
+
`aria-busy` and `aria-disabled`, a spinner over the label, the press swallowed, the
|
|
592
|
+
width and the focus kept, as `Button`'s. A link and a `checkbox` / `radio` chip ignore it. (Z-091)
|
|
593
|
+
- `Checkbox` and `Radio` take `error`, `variant`, `message` and `hint`, drawn under the
|
|
594
|
+
label as on `Input`: an error is an alert, sets `aria-invalid` on a checkbox and is
|
|
595
|
+
tied to the input with `aria-describedby` (after the hint). `Toggle` is unchanged. (Z-090)
|
|
596
|
+
|
|
597
|
+
### Fixed
|
|
598
|
+
|
|
599
|
+
- `Skeleton` no longer pulses under `prefers-reduced-motion`: it is a still fill
|
|
600
|
+
(`motion-reduce:animate-none`). A `motion-reduce:animate-pulse` class on the
|
|
601
|
+
skeleton brings the pulse back. (Z-093)
|
|
602
|
+
- `RadioGroup`: an option's `description` is no longer part of the radio's name; it is
|
|
603
|
+
`aria-hidden` and tied to the radio with `aria-describedby`, as `Radio`'s. A one-line
|
|
604
|
+
option is centred in its row (44px on touch) and its circle loses `mt-0.5`; a row
|
|
605
|
+
with a description keeps the radio at the top. A radio found by its two-sentence
|
|
606
|
+
name is now found by its label. (Z-092)
|
|
607
|
+
- `Select` no longer outgrows its container: a width class (`w-48`) in a narrower
|
|
608
|
+
column is capped at the column (`.select-container { max-width: 100% }`, in the
|
|
609
|
+
components layer, so a `max-w-*` class of your own still wins). (Z-089)
|
|
610
|
+
|
|
611
|
+
## [9.0.0-alpha.11] - 2026-10-07
|
|
612
|
+
|
|
613
|
+
An eleventh local preview build of the 9.0 direction, not published to npm. One
|
|
614
|
+
fix for the app team's report Z-088; no token, prop, variant or export of
|
|
615
|
+
alpha.10 is removed or renamed. Run on this build: the type and design checks,
|
|
616
|
+
the unit suite, the package build and a scratch install, and one run in
|
|
617
|
+
Playwright's WebKit (an iPhone 15 profile, not iOS Safari).
|
|
618
|
+
|
|
619
|
+
### Fixed
|
|
620
|
+
|
|
621
|
+
- `BottomSheet` no longer rises to the top of the screen as it opens on WebKit,
|
|
622
|
+
and no longer rests 32px above the bottom edge. The overlay root was
|
|
623
|
+
`overflow: hidden`, a scroll container, and WebKit scrolled it to the focused
|
|
624
|
+
grip while the panel was still below the screen; it is now `overflow: clip`.
|
|
625
|
+
The slide's own spring (a 15px overshoot at half) is unchanged. To restore the
|
|
626
|
+
old root: `overflow-hidden` for `overflow-clip` on the root in
|
|
627
|
+
`BottomSheet.svelte`.
|
|
628
|
+
|
|
629
|
+
## [9.0.0-alpha.10] - 2026-10-07
|
|
630
|
+
|
|
631
|
+
A tenth local preview build of the 9.0 direction, not published to npm. One
|
|
632
|
+
addition for the app team's request Z-087; no token, prop, variant or export of
|
|
633
|
+
alpha.9 is removed or renamed, and nothing an app shows changes without a
|
|
634
|
+
change to its code. Run on this build: the type and design checks, the unit
|
|
635
|
+
suite, the package build and a scratch install. Not run: Playwright or any
|
|
636
|
+
look in a browser.
|
|
637
|
+
|
|
638
|
+
### Added
|
|
639
|
+
|
|
640
|
+
- `Avatar` `children`: a snippet drawn inside the circle in place of `src` and
|
|
641
|
+
the initials, for an inline SVG that reads the page's tokens. It is
|
|
642
|
+
presentational; `name` stays the accessible name, and the circle keeps its
|
|
643
|
+
size.
|
|
644
|
+
- `AvatarGroup` people take `picture?: Snippet`, the same for one person.
|
|
645
|
+
- A list row's `avatarPicture?: Snippet` (`ListItemData`): the same in the
|
|
646
|
+
row's round; wins over `avatar` and `icon`; decorative unless `avatarAlt`
|
|
647
|
+
names it.
|
|
648
|
+
|
|
649
|
+
## [9.0.0-alpha.9] - 2026-10-07
|
|
650
|
+
|
|
651
|
+
A ninth local preview build of the 9.0 direction, not published to npm. Two
|
|
652
|
+
additions for the app team's request Z-086; no token, prop, variant or export
|
|
653
|
+
of alpha.8 is removed or renamed, and nothing an app shows changes without a
|
|
654
|
+
change to its code. Run on this build: the type and design checks, the unit
|
|
655
|
+
suite, the package build and a scratch install. Not run: Playwright or any
|
|
656
|
+
look in a browser.
|
|
657
|
+
|
|
658
|
+
### Added
|
|
659
|
+
|
|
660
|
+
- `RadioGroup` `chosenRing` (default `true`). `chosenRing={false}` leaves the
|
|
661
|
+
filled radio to show the chosen row: the row's own ring is gone, and keyboard
|
|
662
|
+
focus keeps its ring.
|
|
663
|
+
- `Radio` `description`: a line under the label, tied to the radio with
|
|
664
|
+
`aria-describedby`; the label alone names the radio.
|
|
665
|
+
|
|
666
|
+
## [9.0.0-alpha.8] - 2026-10-07
|
|
667
|
+
|
|
668
|
+
An eighth local preview build of the 9.0 direction, not published to npm. Four
|
|
669
|
+
additions for the app team's requests Z-082 to Z-085; no token, prop, variant
|
|
670
|
+
or export of alpha.7 is removed or renamed. One default changes what an app
|
|
671
|
+
shows without a change to its code: a `Select` in a sheet with up to six
|
|
672
|
+
options and no search field gets a sheet as tall as its list; `sheetFit={false}`
|
|
673
|
+
restores the half-height sheet. Run on this build: the type and design checks,
|
|
674
|
+
the unit suite, the package build and a scratch install. Not run: Playwright or
|
|
675
|
+
any look in a browser.
|
|
676
|
+
|
|
677
|
+
### Added
|
|
678
|
+
|
|
679
|
+
- `Chip type="submit"` (Z-082; D185): a button chip that posts its form with `name` and `value`, and does not toggle; `selected` is the app's and is drawn as on a toggle and said with `aria-pressed`. `type="button"` is the same without the submit. Without a `type` a chip is the toggle button it was.
|
|
680
|
+
- `Select` options take `lang` (Z-083; D186): set on the option's row, on the trigger's text while it is chosen and on the native `<option>`.
|
|
681
|
+
- `Select sheetFit`, `Dropdown sheetFit` and `BottomSheet fit` (Z-084; D187): a sheet as tall as its content, up to half the screen, with one resting height.
|
|
682
|
+
- `Card type="submit" | "button" | "reset"` with `name`, `value`, `form` and `disabled` (Z-085; D188): the whole card is one `<button>`, drawn and pressed as a link card is, with the 44px minimum. With `href` it is still a link.
|
|
683
|
+
|
|
684
|
+
### Changed
|
|
685
|
+
|
|
686
|
+
- A `Select` shown in a sheet, with up to six options and `searchable={false}`, now gets a sheet as tall as its list instead of half the screen (Z-084; D187). Restore the half sheet with `sheetFit={false}`.
|
|
687
|
+
|
|
688
|
+
## [9.0.0-alpha.7] - 2026-10-07
|
|
689
|
+
|
|
690
|
+
A seventh local preview build of the 9.0 direction, not published to npm. One
|
|
691
|
+
addition on top of alpha.6; no token, prop, variant or export is removed or
|
|
692
|
+
renamed, and with the new properties unset every `Badge` renders as before.
|
|
693
|
+
|
|
694
|
+
### Added
|
|
695
|
+
|
|
696
|
+
- `Badge` neutral subtle fill and text (Z-081; D184): `--zabi-badge-neutral-fill` and `--zabi-badge-neutral-text`, declared nowhere and read in front of `--color-neutral-subtle` and `--color-neutral-text`. Set on any ancestor or in the theme, in light and dark, they move the neutral subtle `Badge` (`variant="neutral"` or `"default"`) and nothing else: `Alert variant="neutral"`, `Skeleton`, `AvatarGroup`, `Header` and the other badge variants keep reading the neutral tokens. New classes `bg-badge-neutral` and `text-badge-neutral` (`@layer utilities`), the first listed beside `bg-neutral-subtle` in the colour-block rules, so a badge in `on-brand`, `on-accent`, `on-fill` and `Card tone` is restored as before. Theme output gains only those lines.
|
|
697
|
+
|
|
698
|
+
## [9.0.0-alpha.6] - 2026-10-07
|
|
699
|
+
|
|
700
|
+
A sixth local preview build of the 9.0 direction, not published to npm; 8.1.0
|
|
701
|
+
stays the stable version. New: the charts drawn in plain SVG (`LineChart`,
|
|
702
|
+
`BarChart`, `PieChart`, `Sparkline`) and `SpeechBubble`. Extended: `Textarea`
|
|
703
|
+
(`resize`, `autogrow`, `maxRows`), `ImageUpload` (`variant="button"`,
|
|
704
|
+
`maxSize`), `ConfirmDialog` (`cancelVariant`). No token, prop, variant or
|
|
705
|
+
export is removed or renamed. Four things change what an app shows without a
|
|
706
|
+
change to its code, each with the line that restores the earlier look in its
|
|
707
|
+
entry below: a dark form field is a well a step lighter than its surface; a
|
|
708
|
+
dark flat `Card` is a translucent layer; a `Textarea` has no resize grip on a
|
|
709
|
+
touch screen; `Input type="password"` has the show/hide button by default.
|
|
710
|
+
Run on this build: the type and design checks, the unit suite, the theme
|
|
711
|
+
tests, the package build and a scratch install. Not run: Playwright, an
|
|
712
|
+
accessibility pass, any look in a browser at the changes since alpha.5 other
|
|
713
|
+
than the charts.
|
|
714
|
+
|
|
715
|
+
### Added
|
|
716
|
+
|
|
717
|
+
- `ImageUpload variant="button"` (Z-076; D183): only the trigger, for a photo that is one choice in a row of others. A `Button` that opens the file chooser (`buttonVariant`, default `secondary`; `buttonSize`, default `md`; its text is `label`, or `children`), with `accept`, `onclick`, `onbrowse`, `onchange`, `onfileselect`, `bind:value`, `disabled`, the error block and the polite announcement as in the dropzone; no drop area, no preview, no Change or Remove. The default `variant="dropzone"` is unchanged.
|
|
718
|
+
- `ImageUpload maxSize` (bytes), in both variants: a larger file, chosen or dropped, is not selected and goes to `onfilereject`, which now gets a second argument, `"type"` or `"size"`. Without `maxSize` nothing changes. The component had no size limit before.
|
|
719
|
+
|
|
720
|
+
- `Textarea autogrow` and `maxRows` (Z-077; D181): the field grows with its content from `rows` up to `maxRows` rows and scrolls after that (no limit without `maxRows`). It uses `field-sizing: content` where the browser has it and measures the content elsewhere. An autogrow field has no resize grip.
|
|
721
|
+
|
|
722
|
+
- `ConfirmDialog cancelVariant` (Z-080; D182): the cancel button's `Button` variant, any `ButtonVariant`. The default is `outline`, so nothing changes; `tonal`, `secondary`, `ghost` or `text` for an app that has no outline button. Cancel stays the first control and keeps the initial focus.
|
|
723
|
+
|
|
724
|
+
- **Charts in plain SVG: `LineChart`, `BarChart`, `PieChart`, `Sparkline`** (Z-004; D156 to D159). The library draws its own charts and has no chart-library dependency; no dependency was added. The three charts are each a `figure` whose `figcaption` is the required `label` (`labelHidden` keeps it for assistive technology only); the drawing is hidden from assistive technology and the same numbers are in a real `table`, off screen unless `showTable`. They take the width of their container and are laid out in real pixels, so text is never scaled; before they are measured (on the server) they are drawn 600 wide and scaled to fit.
|
|
725
|
+
- `LineChart`: `series` (`{ name, points, color? }[]`; `null` in `points` leaves a gap), `xLabels`, `yMin`, `yMax`, `yFormat` (`'number'`, `'percent'` where 72 is "72 %", or a function), `height` (px, default 240), `legend` (default: on for more than one series), `markers` (default on: a different shape per series, so lines differ by more than colour), `xTitle`, `locale`, `glass`. Pointing at or tapping the plot shows the values at that x label in a pane of the thick material that floats over the plot beside the guide, as a `Tooltip` does; there is no keyboard route to that readout, the table is.
|
|
726
|
+
- `BarChart`: `data` (`{ label, value, color? }[]`), `color`, `yMax`, `yFormat`, `height`, `showValues` (default on), `categoryTitle`, `valueTitle`, `locale`. One set of upright bars from a zero line; values below zero are drawn downward.
|
|
727
|
+
- `PieChart`: `data`, `donut`, `center` (a snippet in the middle of the ring), `format`, `size` (px, default 192; shrinks with the container), `legend` (default on: each slice's label, value and share), `categoryTitle`, `valueTitle`, `shareTitle`, `locale`. Slices run clockwise from twelve o'clock in the order given and are parted by a thin cut, so they need no border colour; in a full pie the cut narrows to a point at the centre, so every slice runs to the middle.
|
|
728
|
+
- `Sparkline` (atom): `points`, `label` (an image by that name; without it, decoration), `color`, `min`, `max`. 5rem by 1.5rem unless `class` says otherwise.
|
|
729
|
+
- Colours: a series without a `color` takes `--zabi-chart-1` to `--zabi-chart-6` by its place; these are declared nowhere and fall back to steps of the library's own ramps: `--color-action-primary` (what a `Progress` fills with), `--color-accent-500`, `--color-brand-900`, `--color-accent-700`, `--color-base-500`, and for a sixth series a plum mixed from the brand and error ramps. None is a status colour. On the default theme each is 3:1 or more on a card in light and dark and any two differ for a reader who does not see red and green. `color` takes one of `primary`, `accent`, `success`, `warning`, `error`, `info`, `neutral`, or any CSS colour; in a chart that names one, a series without a `color` steps past the series colour nearest to it. In forced colours the marks are drawn in `CanvasText`.
|
|
730
|
+
- Built from the library's parts: the caption is set like a `Progress` label; a bar is a `Progress` fill stood on end (at most 24px wide, a round far end) and with a `yMax` each bar stands in a `Progress` track up to it, the values in a row over the tracks; grid lines are `--color-border` and the base line `--color-border-strong`; the `PieChart` key is set like `List` rows (name, value, share; parted in `--zabi-list-divider-color`) and takes the width beside the drawing, or the full width under it when the chart itself is narrower than 26rem; `showTable` shows the library's `Table`, numbers at the end of their column; a donut's ring is a third of the radius and takes a `center` snippet (a `Stat` fits); lines are 2px with 8px markers, each in a ring cut out of the lines so it shows whatever is behind the chart (`--zabi-chart-surface` paints the ring instead); a `Sparkline` ends in a dot on its last value. Chart text is 0.75rem with tabular numerals and the value axis is as wide as its widest label as drawn, so both follow the reader's text size.
|
|
731
|
+
- Glass marks: the slices of a `PieChart` and the bars of a `BarChart` are drawn as glass by default: the series colour, opaque, under the veil of a solid control, with one pixel of light on the top of the rim, a soft sheen under it and a soft shadow. No backdrop blur is involved (an SVG shape takes none), so it reads on a plain card too. Flat fills with reduced transparency, more contrast, forced colours and inside `data-materials="opaque"`. Line markers, key marks and the `Sparkline` dot stay flat.
|
|
732
|
+
- Glass panel: `glass` on `LineChart`, `BarChart` and `PieChart` stands the chart on a panel of the thick material (off by default: it needs a wash, a tint or a picture behind it, and on a plain card it shows only an edge and a shadow). It is the library's `material-thick`, so reduced transparency, more contrast, forced colours, no `backdrop-filter` and `data-materials="opaque"` make it the opaque surface. Over a solid brand or accent fill some series colours fall under 3:1 on it; THEMING.md has the numbers.
|
|
733
|
+
- Types `ChartSeries`, `ChartDatum`, `ChartColor`, `ChartColorName`, `ChartFormat` are exported. Not included: stacked or grouped bars, horizontal bars, areas, a second axis, zoom, animation.
|
|
734
|
+
- **`SpeechBubble`** (Z-075; D155): a quotation in a bubble with a drawn tail. A `figure` whose `figcaption` is `label` (a string or a snippet; `labelHidden` keeps it for assistive technology only) and whose content is a `blockquote` with the children as real, selectable text. `side` (`end` default, `start`; follows the text direction) places the tail; `tone` is `ink` (default: the headline colour as fill and the page colour as text, so dark in light and light in dark) or `paper` (the two swapped, for a bubble on a filled block). `decoration` is a snippet drawn on the top corner of the tail's side. Four custom properties declared nowhere, set on the component or any ancestor: `--zabi-bubble-fill` and `--zabi-bubble-text` (win over `tone`), `--zabi-bubble-radius` (any `border-radius` value, the eight-value form included; default `1.5rem`) and `--zabi-bubble-tilt` (turns the fill and tail, not the text; default `0deg`). The tail is an inline SVG in the fill colour, sized in rem; tail, fill and decoration are hidden from assistive technology; in forced colours the bubble is a 2px outline and the tail is not drawn. The type is `text-lg font-medium` on the figure and can be replaced through `class`. Not included: a copy action and a speaker slot.
|
|
735
|
+
|
|
736
|
+
### Changed
|
|
737
|
+
|
|
738
|
+
- `Textarea` has no resize grip on a touch screen (Z-077; D181). New prop `resize`: `auto` (default: the browser's vertical grip with a mouse or trackpad, none where the pointer is coarse), `vertical`, `none`. On a phone the grip was 16px in the field's corner and dragging it scrolled the page. Desktop is unchanged. The earlier behaviour everywhere: `resize="vertical"`. A `resize-*` class passed in `class` still wins.
|
|
739
|
+
|
|
740
|
+
- **In dark, a form field is a well of its own, not the page colour inside an edge** (Z-078; D180). `--color-input` in dark is a translucent step of the neutral ramp (base-50 at 7%; `-hover` 10%, `-focus` 7%, `-active` 15%) instead of the page step, so `Input`, `Textarea`, `Select`, `DateField`, `TimeField` and `PickerField` are a little lighter than whatever they sit on: 1.20:1 off the page, 1.22:1 off a card, 1.23:1 off a sheet on the default theme, with no rule per surface. Light is unchanged. The field edge (`--color-input-border`) is unchanged and stays 3:1 against the surface outside the field; against the lifted fill inside it is 3.05:1 on the page, 2.79:1 on a card and 2.50:1 on a sheet. The dark placeholder is a shade lighter (`#ababb3`, was `#a1a1aa`) to keep 4.5:1 on the lifted fill; on a pressed field in a sheet it is 4.08:1. New tokens, additive: `--color-input-solid`, `-solid-hover`, `-solid-focus`, `-solid-active`, the opaque field a colour block (`.on-brand`, `.on-accent`, `.on-fill`) uses, where a translucent fill would sit on the block's colour. New guard pairs in `check:contrast` and in `createTheme`'s check, dark only, laid over the page, a card and the overlay: the well against its surface (1.15:1), value and placeholder on every field state, the hover and pressed steps, the edge on the fill. `--color-surface-inset`, `--color-input-disabled` and the progress track are unchanged. Restore the earlier dark field: `overrides: { dark: { '--color-input': 'var(--color-surface-inset)', '--color-input-hover': 'var(--color-base-200)', '--color-input-focus': 'var(--color-base-150)', '--color-input-active': 'color-mix(in srgb, var(--zabi-base-50) 10%, var(--color-surface-inset))', '--color-input-placeholder': 'var(--color-base-600)' } }`.
|
|
741
|
+
|
|
742
|
+
- `Input type="password"` has the show/hide button by default (`revealable` now defaults to `true`). Pass `revealable={false}` for a password field without it. Other types are unaffected.
|
|
743
|
+
|
|
744
|
+
- In dark, `Card variant="flat"` is a translucent, slightly lighter layer (the neutral ramp's base-50 at 7%, hover 11%, pressed 15%) instead of the opaque card fill, so flat cards nested in each other, in a default card, an elevated card or a sheet each read a step lighter and stay visible. Light is unchanged: a light flat card computes to the same fill as before. New tokens `--color-card-flat`, `--color-card-flat-hover`, `--color-card-flat-active` (utilities `bg-card-flat`, `hover:bg-card-flat-hover`, `active:bg-card-flat-active`), settable through `createTheme` `overrides`. To keep the opaque dark fill: `overrides: { dark: { '--color-card-flat': 'var(--color-card)' } }`, and the same for `-hover` and `-active` (`var(--color-card-hover)`, `var(--color-card-active)`).
|
|
745
|
+
|
|
746
|
+
### Fixed
|
|
747
|
+
|
|
748
|
+
- `BottomSheet`: the panel's height no longer transitions while the sheet slides in (Z-079). The height transition is for snapping and for settling a drag; during the slide-in a late change of height rode the spring and moved the content after it had arrived. It is switched on when the slide has ended (at once under reduced motion). The slide itself and the snaps keep the spring.
|
|
749
|
+
|
|
750
|
+
- `Input type="search"` with a clear button of its own in `trailing` no longer also shows the browser's clear cross in Chrome and Safari. A search field without `trailing` keeps the browser's.
|
|
751
|
+
|
|
752
|
+
## [9.0.0-alpha.5] - 2026-10-07
|
|
753
|
+
|
|
754
|
+
A fifth local preview build of the 9.0 direction, not published to npm; 8.1.0
|
|
755
|
+
stays the stable version. It is the components the consuming app asked for.
|
|
756
|
+
New: `Chip` and `ChipGroup` (a link, a toggle button or a real radio or
|
|
757
|
+
checkbox input under one tonal selected style, in a wrap or a one-row layout
|
|
758
|
+
that scrolls sideways), `Block` (the full-width colour section), `Stat`,
|
|
759
|
+
`PickerField` (a field-shaped trigger for an app's own sheet). Extended: `Card`
|
|
760
|
+
is linkable (`href`) and takes a `tone` or a custom `fill`; `Collapsible` is a
|
|
761
|
+
native `<details>` and opens without scripts; `Progress` gets a 16px size, a
|
|
762
|
+
segmented form and a track edge token; `Rating` a compact score, 40px stars
|
|
763
|
+
and a soft empty state; `Heading` a display variant on a display typeface
|
|
764
|
+
token; `Checkbox`, `Radio` and `Toggle` a `leading` snippet; `Alert` an edge
|
|
765
|
+
that can be turned off and a brand variant; `DateField` and `TimeField` a
|
|
766
|
+
`locale` with the library's own display and picker over the native input.
|
|
767
|
+
No token, prop, variant or export is removed or renamed. Four things change
|
|
768
|
+
what a consumer sees or gets without a code change, each listed under Changed
|
|
769
|
+
or Fixed with what restores it where something does: the default
|
|
770
|
+
`Collapsible` trigger is a `<summary>` instead of a `<button>`; a solid
|
|
771
|
+
control in a colour block's own colour is inverted inside that block; a
|
|
772
|
+
`Stepper` bar's upcoming segments are filled with the progress track; and the
|
|
773
|
+
floating `BottomTabBar` is 72px tall with equal insets. Built quickly and
|
|
774
|
+
tested lightly on purpose: the type and design checks, the unit run, the theme
|
|
775
|
+
tests, the package build and a scratch install were run on it, and the new
|
|
776
|
+
components' stories were looked at once in Storybook with headless Chromium in
|
|
777
|
+
light and dark at phone and desktop widths (two things were fixed from that
|
|
778
|
+
look). No browser suite was run, there was no accessibility or QA review, and
|
|
779
|
+
nothing was tried on a real phone, in Safari or Firefox, without scripts in a
|
|
780
|
+
real browser, at 200% text, right-to-left, in forced colours, or with a
|
|
781
|
+
keyboard or a screen reader.
|
|
782
|
+
|
|
783
|
+
### Added
|
|
784
|
+
|
|
785
|
+
- **Chip tokens** (Z-051): `--color-chip`, `-hover`, `-active`, `-text` (a quiet neutral fill without an edge: `base-200` / `300` / `350` in light, `--zabi-base-750` / `700` / `650` in dark, the label is the headline colour) and `--color-chip-selected`, `-hover`, `-active`, `-text`, which default to the tonal role (`--color-action-tonal*`), so an app that themes tonal gets matching chips. Classes `bg-chip`, `hover:bg-chip-hover`, `active:bg-chip-active`, `text-chip-text`, `bg-chip-selected`, `hover:bg-chip-selected-hover`, `active:bg-chip-selected-active`, `text-chip-selected-text`. Label pairs guarded at 4.5:1 on all fills in both modes, in the library and in `createTheme`.
|
|
786
|
+
- **`--color-card-tint`** (`bg-card-tint`; Z-055): the brand tint as a surface for content. `brand-100` in light, the brand at 8% over the card in dark. Text roles and the link are guarded on it at 4.5:1, the focus ring, the field edge and the outline-button edge at 3:1.
|
|
787
|
+
- **`.on-fill`** (Z-055): the on-colour scope for a fill the app chooses. Like `.on-brand`, with the label colour taken from `--zabi-on-fill` (fallback `currentColor`): text roles, links, edges and focus rings inside take it, and a surface inside (a Card, a field, a Chip) gets the theme's roles back. The library cannot check the contrast of a colour picked at run time.
|
|
788
|
+
- **`--color-progress-track-border`** (`border-progress-track-border`; Z-049): the edge of the Progress track, by default the field edge as before. `createTheme` writes `transparent` for a mode in which an overridden `--color-progress-track` itself reaches 3:1 against the page and the card and the app did not set the edge; an edge the app sets itself is never moved.
|
|
789
|
+
- **`--font-family-display`** (`font-display`; Z-058): a second typeface as a token; follows `--font-family-heading` until set. `cn` knows `font-heading` and `font-display` as font families.
|
|
790
|
+
- **`Checkbox`, `Radio` and `Toggle` take a `leading` snippet** (Z-060; D145): drawn inside the one `<label>`, between the control and the label text (an avatar in an attendee list), so a press on it toggles the control. The accessible name stays the `label` string; give a leading image an empty alt. Without `leading` the markup is unchanged.
|
|
791
|
+
- **`Chip`** (Z-051; D144): one pill, four forms. With `href` it is a link (`selected` sets `aria-current`, `current` picks its value) and works without scripts; with `type="checkbox"` or `type="radio"` it is a real input inside a label, which submits in a form without scripts and whose selected look follows the native `:checked`, so a choice made before hydration shows at once and is kept; otherwise it is a toggle button with `aria-pressed`. One selected style in every form and both modes: the tonal fill and label (`--color-chip-selected*`) and a semibold label, no outline and no solid fill; a system-colour outline in forced colours. Props: `selected` (bindable), `size` (`sm` 28px, `md` 32px, `lg` 40px; a 44px target on coarse pointers without growing the box), `leading`, `checkmark` (a check while selected; on for a checkbox and a toggle button, off for a link and a radio), `disabled`, `name`, `value`, `required`, `form`, `onchange`. A repeat press on the selected radio chip does nothing; a checkbox or toggle chip turns off.
|
|
792
|
+
- **`ChipGroup`** (Z-051, Z-059; D144): `layout="wrap"` (default) or `layout="row"`, one line that scrolls sideways without a scrollbar, with the chosen chip brought into view on load (only the row scrolls; `scrollSelectedIntoView={false}` turns it off) and an edge cue, `edge="fade"` (default: a mask on a side that has more chips beyond it, so it works on any background; none on the server or before hydration) or `edge="none"`. `label` (or `aria-label` / `aria-labelledby`) names the group, `showLabel` shows it. With `type="radio"` or `type="checkbox"`, `name` and `value` (bindable; a string, or an array for checkboxes) the chips inside are inputs that take only a `value`. A `ChipGroup` inside a `ChipGroup` is a named sub-group in the same row or wrap, with a wider gap (24px) between sub-groups.
|
|
793
|
+
- **`Progress size="xl"`** (16px) and **`Progress segmented`** (Z-049; D147): `segmented` draws `max` separate segments with `value` of them filled, for a count such as 4 of 19 (`max` a whole number from 2 to 24; any other `max` draws the continuous bar and warns in a dev build). Still one `role="progressbar"`, with `aria-valuetext` from the new string `progress.valueText(value, max)` ("4 of 19"; set it once in `ZabiStringsProvider` or per instance with `strings`), and with `label` the read-out is that text instead of the percent.
|
|
794
|
+
- **`Card href`** (Z-042; D146): the card itself is an `<a>` (with `target`, `rel`), so the whole card is the link and it works without scripts: focus ring on the card edge, hover and pressed states, a 44px minimum, the content keeps the body colour. A link card must not hold other interactive elements.
|
|
795
|
+
- **`Card tone`, `fill` and `onFill`** (Z-055; D146): `tone="neutral"` (default, unchanged), `"tint"` (`--color-card-tint`), `"brand"` (the primary fill with the `on-brand` scope) or `"accent"`; `fill` with `onFill` is a colour the app chooses with its label colour (the new `on-fill` scope; the app holds that pair to 4.5:1, the library cannot check it). A toned card has no border and no shadow (unless `variant="elevated"`). Text, links, outline and ghost buttons, edges and focus rings inside take the fill's label colour; a neutral Card, a field or an Alert inside is a surface with the theme's colours again. An interactive toned card shows hover and pressed as a veil of its own text colour.
|
|
796
|
+
- **`Block`** (Z-055; D146): the full-width, square-cornered colour section, with the same `tone`, `fill` and `onFill`, `padding` (`none`, `sm`, `md`, `lg`) and `as` (`div`, `section`, `header`, `footer`, `article`, `aside`). It does not break out of a padded parent by itself: place it outside the padded container or give it a negative inline margin through `class`.
|
|
797
|
+
- **`Rating compact`** (Z-054, Z-066; D149): a score for a list, not a control: one filled star and the number ("4,0", through `formatValue`), from the real `value` and `max`, as one image with one accessible name ("4 of 5 stars", the readonly string). It implies `readonly`. Sizes `sm` (14px), `md` (16px), `lg` (20px), `xl` (28px star, 30px number) and `display` (40px star, 48px number in the display face).
|
|
798
|
+
- **`Rating size="xl"`** (40px stars, for a large input) and **`Rating empty="soft"`** (Z-054; D149): an empty star as a soft filled shape with no outline, in `--zabi-rating-off-fill` (falls back to `--color-control-track`; declared nowhere, set it on any ancestor). The soft fill is below 3:1 against the page by default, so it suits a rating with a visible label and value; in forced colours it is an outline. The default stays `empty="outline"`.
|
|
799
|
+
- **`Stat`** (Z-058; D150): a figure with a label. `value`, `label`, `unit` (after the value, smaller, on the same baseline: `value="4" unit="av 19"`), `size` (`sm` 24px, `md` 36px, `lg` 48px), `align`, `labelPosition` (`below`, `above`), or children for a figure that needs markup. The value is in the display face with tabular figures; plain text in reading order, no role.
|
|
800
|
+
- **`Heading variant="display"`** (Z-058; D150): the display face (`--font-family-display`) with its own steps: 60px (48px below `sm`), 48 (36), 36 (30), 30, 24 and 20px by `size ?? level`, balanced wrapping. Its weight and tracking are two custom properties declared nowhere, shared with `Stat`: `--zabi-display-weight` (default 700) and `--zabi-display-tracking` (default -0.02em). So a second typeface is three properties on any ancestor and no CSS rule. `variant="heading"` (default) is unchanged.
|
|
801
|
+
- **`Alert bordered`, `--zabi-alert-border-width` and `Alert variant="brand"`** (Z-072; D152). `bordered={false}` makes the edge transparent (the box keeps its size); the edge's width reads `--zabi-alert-border-width` (default `1px`, declared nowhere), so `--zabi-alert-border-width: 0px` on `:root` or any ancestor removes the edge from every Alert under it with no prop. `variant="brand"` is the theme's brand tint (`--color-action-primary-subtle`, edge `--color-action-primary-border`, icon in the link colour), with `role="status"`. `variant="neutral"` already existed. The default Alert is unchanged.
|
|
802
|
+
- **`PickerField`** (Z-071; D151): a field-shaped trigger that opens whatever the app gives it (its own sheet with search, a page). It looks and measures like a `Select` of the same `size`, with `label`, `hint`, `error` (and `variant` / `message`) wired as on the other fields, `value` (the text of what is chosen), `placeholder`, a `leading` snippet, children for a rich value, and a chevron. It is a `<button aria-haspopup="dialog">` named by the label and the value (`haspopup`, `expanded` and `controls` set the ARIA attributes), `bind:element` hands the app the trigger to return focus to, and `name` with `formValue` submit the chosen id through a hidden input. With `href` it is a link, for a picker that is a page and works without scripts.
|
|
803
|
+
- **`DateField` and `TimeField` take a `locale`** (Z-053; D153). Without it nothing changes. With it, the server and the page before hydration still render the native `<input type="date|time">` (the fallback without scripts; it submits as before). Once the page runs, the native input stays as the form control, out of sight, and a field-shaped button takes its place in the same box, showing the value in the app's locale through `Intl.DateTimeFormat` ("10 nov. 2026", "18:30", never the browser's own format), with `format` (the `Intl` options, for "tis 6 okt") and `placeholder`. Pressing it opens the library's picker in a `BottomSheet` titled by the label: the `Calendar` for a date (choosing a day sets the value, closes and returns focus; `min` and `max` are honoured), and two listbox columns, hours and minutes, for a time (hours labelled as the locale writes them, minutes every `step`, 5 minutes without one, a value off the step kept as its own option; Done commits). Clear is offered when the field is not `required`. `picker="native"` keeps the locale display and opens the platform's picker with `showPicker()` instead (and a `step` under a minute does the same); where that is refused the native input comes back. `input` and `change` fire on the native input for a value set by the picker, and a `required` empty field moves focus to the button instead of showing a bubble on a hidden control. New strings `dateField` and `timeField` (`clear`, `done`, `hours`, `minutes`, `chooseDate`, `chooseTime`) through `ZabiStringsProvider` or `strings`. The sheet is used at every width in this build. `PickerField` gains a `trailing` snippet that replaces its chevron.
|
|
804
|
+
|
|
805
|
+
### Changed
|
|
806
|
+
|
|
807
|
+
- **`Collapsible` opens without scripts** (Z-041; D143). With the default trigger (`title`) it is now a native `<details>` / `<summary>`: it opens before hydration and for a visitor without scripts, a toggle made before hydration is kept, find-in-page opens it, and a single-open `CollapsibleGroup` gives its panels a shared `name` so the browser keeps them exclusive too. Markup change: the default trigger is a `<summary>` (it keeps `id`, `data-collapsible-trigger` and `triggerClass`), not a `<button>` with `aria-expanded`, the heading of `headingLevel` is inside it, and the panel is no longer `hidden` while closed (a form inside a closed panel keeps its values and still submits them, as before). A test that looked for `role="button"` needs the summary instead. A `disabled` panel has `aria-disabled` and leaves the tab order. With a custom `trigger` snippet nothing changes, and that path still needs scripts. No prop is added or removed.
|
|
808
|
+
- **`Progress` reads its track edge from `--color-progress-track-border`** (class `border-progress-track-border`, was the hard-coded `border-input-border`; Z-049). The default value is the same field edge, so nothing moves until the token is set (or `createTheme` sets it to `transparent` for a track that reaches 3:1 on its own).
|
|
809
|
+
- **`Stepper`, bar layout: an upcoming segment is a filled track** (Z-050; D147): fill `--color-progress-track`, edge `--color-progress-track-border`, instead of a hollow `--color-control-border` outline. With the default tokens it looks nearly as it did (the field fill inside the field edge); an app that themes the progress track gets a filled, edgeless track. Restore the 8.1 outline on any ancestor: `--zabi-stepper-upcoming-fill: transparent; --zabi-stepper-upcoming-edge: var(--color-control-border)`.
|
|
810
|
+
- **A solid control in the block's own colour is inverted inside a block** (D148). Inside `.on-brand`, `.on-accent` and `.on-fill` (so inside `Card tone="brand"`, a brand `AppBar`, or a block an app already marks), an element with `bg-action-primary` on a brand block (a primary `Button`, a solid brand `Badge`, a checked `Checkbox`), or with `bg-accent` on an accent block, was the same colour as the block; it now takes the block's label colour as its fill and the block's colour as its text, with hover and pressed mixed from the two, and a filled `Rating` star takes the label colour. A surface inside the block restores the theme's values. Outside a block nothing changes.
|
|
811
|
+
- **`AppBar`: the row's fixed pixel scale is documented** (Z-074) on the `leading` and `actions` props: the row sets `--spacing: 4px`, so content placed there should be sized on it (`size-9`, or `calc(var(--spacing) * 9)`), not in rem, or it grows at 200% text while the bar's own controls do not. No behaviour change.
|
|
812
|
+
|
|
813
|
+
### Fixed
|
|
814
|
+
|
|
815
|
+
- **The floating `BottomTabBar` capsule is concentric with its tabs** (D154): the tabs sat 8px inside the capsule at its ends but 4px above and below, under a 28px corner. They now sit the same distance inside on all four sides (the list's gap: 8px, less in a very narrow bar), and the capsule's radius is the tab's 20px plus that distance, so outer radius = inner radius + padding holds at every width. The floating bar is 72px tall (was 64px); `--app-shell-bottom-inset` follows, it is measured. The edge-to-edge bar is unchanged.
|
|
816
|
+
- **`Alert`: the close button's radius follows `--zabi-alert-border-width`**, so it stays concentric with the alert when the edge is set to `0px`.
|
|
817
|
+
- **The active tab's forced-colours outline is the system highlight colour** in `BottomTabBar` and `AppNavigation`: the class `forced-colors:outline-(color:Highlight)` was never emitted (that form is for custom properties), so the outline fell back to the text colour. It is `forced-colors:outline-[color:Highlight]` now. `ring-(color:--color-bar)`, the ring around a tab's count, was checked in the built stylesheet and is emitted.
|
|
818
|
+
- **`TimeField` picker: the chosen minute is in view and the chosen row is legible** (found in the look at this build): the columns centred their chosen row from `offsetTop`, which counts from the sheet, so only the focused hours column was right; and the chosen row's label kept the body colour on the primary fill in light. Both corrected before the build was packed.
|
|
819
|
+
- **Storybook: `Heading / All levels`, `Display` and `Display with own face` render** (they passed a string as a component), and the `Stat` row on a brand block wraps on a phone.
|
|
820
|
+
|
|
821
|
+
## [9.0.0-alpha.4] - 2026-10-06
|
|
822
|
+
|
|
823
|
+
A fourth local preview build of the 9.0 visual direction, not published to npm;
|
|
824
|
+
8.1.0 stays the stable version. It is dark mode on its own terms: the dark
|
|
825
|
+
surfaces are steps of the neutral ramp, so a tinted neutral keeps its hue and
|
|
826
|
+
chroma on every level, in two tone steps, with overlays set apart by a rim and
|
|
827
|
+
a shadow instead of by grey; the primary and danger buttons keep their colour
|
|
828
|
+
in dark under a white label; the dark status tints are low-chroma tints of the
|
|
829
|
+
card; and dark sheets, menus and bars show glass again, because the dark
|
|
830
|
+
material dims what is behind it. Light does not change, with one exception:
|
|
831
|
+
the light thick material is 88% instead of 86%. `createTheme` gains
|
|
832
|
+
`darkPrimary`, now checks the material pairs it was silently skipping (a theme
|
|
833
|
+
may get a warning it did not get before), and keeps the ramp's ink in the mode
|
|
834
|
+
an override did not name. AppShell gains `flushTop` and AppBar a fourth tone,
|
|
835
|
+
`inherit`; `cn` merges the named spacing steps. No token, prop, variant or
|
|
836
|
+
export is removed or renamed; `tokens/surface-ladder.js` (not a published
|
|
837
|
+
file) exports steps instead of wash alphas. Several dark defaults change what a
|
|
838
|
+
consumer sees, and each has a line that restores the earlier look. Built
|
|
839
|
+
quickly and tested lightly on purpose: the type and design checks, the unit
|
|
840
|
+
run, the theme tests, the package build and a scratch install were run on it,
|
|
841
|
+
and the shell, Card, Alert, Badge, Button, Progress, Toggle, SegmentedControl,
|
|
842
|
+
Select, Modal and BottomSheet stories were looked at once in Storybook in dark
|
|
843
|
+
and light at phone and desktop widths. No browser suite was run (the library's
|
|
844
|
+
Playwright specs will need updating for the new looks), there was no
|
|
845
|
+
accessibility or QA review, and nothing was tried on a real phone, in Safari or
|
|
846
|
+
Firefox, at 200% text or with a keyboard.
|
|
847
|
+
|
|
848
|
+
### Added
|
|
849
|
+
|
|
850
|
+
- **`AppShell flushTop`** (Z-065; D140): without a `header`, the content no longer gets the top safe-area padding, so a colour block at the top of a tab screen runs under the status bar and the content handles the safe area itself (`--app-shell-top-inset` keeps its value; an `AppBar position="static"` in the block pads by it). Default `false`; with a `header` it does nothing.
|
|
851
|
+
- **`AppBar tone="inherit"`** (Z-067; D140): a bar inside a block that is not the primary colour. No fill of its own, never the glass or the hairline, and the title, back control, ghost actions, links, borders and focus rings take the text colour the block gives (the `on-brand` scope with `--color-on-brand: currentColor`). A sticky bar that must cover scrolling content takes its fill from the app: `class="[--color-bar:var(--your-block-colour)]"`. Contrast inside the block is the app's to check. Story: "Design System/Organisms/AppShell (9.0) / Colour Block Under Status Bar".
|
|
852
|
+
|
|
853
|
+
### Changed
|
|
854
|
+
|
|
855
|
+
- **Dark surfaces are steps of the neutral ramp** (Z-044; D133, D134). `--color-surface-raised` is `var(--zabi-base-850)` (`#1f1f22`, was a 6.4% white wash, `#262629`), `--color-surface-elevated` and `--color-surface-overlay` are both `var(--zabi-base-800)` (`#27272a`; were `#363638` and `#454547`), `--color-surface-overlay-hover` is `var(--zabi-base-750)` and `--color-surface-inset` (and so `--color-input`) is the page step, `var(--zabi-base-900)`. The page does not move. Two tone steps in all (OKLCH L 21.0, 24.1, 27.4), and with a tinted neutral every surface keeps the ramp's hue and chroma instead of greying as it rises. An overlay is no longer a lighter grey than a card: it is set apart by its rim (`--color-material-rim`, 12% to 14% of `--zabi-base-50`: 1.85:1 on the page, 1.72:1 on a card, 1.56:1 on the overlay) and a stronger dark shadow (`--shadow-opacity` 0.24 to 0.5, `--shadow-opacity-contact` 0.3 to 0.4). What follows the ladder in dark: `--color-border` and `--color-border-weak` are `base-250` (`#333338`: 1.41:1 on the page, 1.31:1 on a card; a quieter hairline than before), `--color-progress-track` is `base-250` (the field is the page colour now), `--color-input-disabled` is `base-75`, `--color-segment-thumb` is `base-400`, `--color-card-active` and `--color-surface-4` are the overlay hover step. Light is unchanged. Restore the 8.1 dark ladder: `overrides: { dark: { '--color-surface-raised': 'color-mix(in srgb, var(--zabi-base-50) 6.4%, var(--zabi-base-900))', '--color-surface-elevated': 'color-mix(in srgb, var(--zabi-base-50) 13.1%, var(--zabi-base-900))', '--color-surface-overlay': 'color-mix(in srgb, var(--zabi-base-50) 19.7%, var(--zabi-base-900))', '--color-surface-inset': 'var(--color-base-150)', '--color-border': 'var(--color-base-200)', '--shadow-opacity': '0.24', '--shadow-opacity-contact': '0.3' } }`.
|
|
856
|
+
- **The primary action keeps the brand hue in dark** (Z-043; D135). The dark primary was the mirrored pale step (`--color-brand-600`, physical step 400) with a dark label; it is now the same physical steps as light, `--color-action-primary: var(--zabi-brand-600)`, `-hover: var(--zabi-brand-700)`, `-active: var(--zabi-brand-800)`, with the light label: `--zabi-on-brand-dark` defaults to `var(--zabi-on-brand)` (white; was `var(--zabi-brand-950)`). White on step 600 is 4.76:1 or more on any ramp; the default fill is 3.65:1 on the dark page, 3.39:1 on a card and 3.07:1 on an overlay. Everything that reads the primary follows: checked Checkbox, Radio and Toggle, the Progress fill, Stepper, the solid brand Badge, the FAB, the brand AppBar. Danger does the same (`--color-action-danger: var(--zabi-error-600)`, 700, 800, label `var(--zabi-error-50)`), so a destructive button is one red in both modes. The focus ring, links, the brand tints, the tonal button and the active navigation fills stay on the mirrored ramp: they are text, rings and tints on a dark surface. With the fill a mid step under a white label, the dark control veil is darken-only as in light (`--gradient-control-top-strength: 0%`, `-bottom-strength: 14%`; were 22% and 6%); the accent keeps 22% and 6% in its own strengths, because its dark fill is still the light step under a dark label. `--color-progress-track` in dark is `base-75` (`#101013`, a well just below the page) so the mid-step fill holds 3:1 on it. Restore 8.1 in one option: `createTheme({ darkPrimary: 'mirror' })` (CLI `--dark-primary mirror`); without the generator, set the three fills to `var(--color-brand-600 | 700 | 800)`, `--zabi-on-brand-dark: var(--zabi-brand-950)` and the two strengths under `.dark`.
|
|
857
|
+
- **`createTheme({ darkPrimary })`** (Z-043): `'brand'` (default, what the library does, nothing extra written) or `'mirror'` (the 8.1 dark fills for primary and danger, the label chosen by the contrast check, the 22% / 6% veil, written under the dark selectors). A `dark` or `both` override of a role wins over both. In dark the label tried first is now white (the ramp's dark end if white fails, so a pale dark primary of the app's own keeps a dark label). With `pin`, a colour that fails in dark falls back to what this option gives, which is step 600 with white by default. An app that overrides `--color-action-primary` for both modes with a semantic step keeps its mirrored dark primary; move the override under `light` to get the brand step in dark.
|
|
858
|
+
- **Four more guarded pairs**: the primary fill and the danger fill against the page and against a card, 3:1, both modes (until now only a pinned colour was held to this).
|
|
859
|
+
- **Dark status tints are low-chroma tints of the card** (Z-057; D136). `--color-success-subtle`, `--color-warning-subtle`, `--color-error-subtle`, `--color-info-subtle`, `--color-energetic-subtle` and `--color-accent-subtle` were the mirrored step 100 (physical 900: the dark error surface was `#660002`); each is now `color-mix(in srgb, var(--zabi-<ramp>-500) 16%, var(--color-surface-raised))`, and the matching `-border` the same mix at 36%. `--color-action-danger-subtle` is the error tint, its hover the mix at 20% and its pressed step at 32%. The hue stays in the icon and the text, which did not move. Light is unchanged. Restore 8.1 per family: `overrides: { dark: { '--color-error-subtle': 'var(--color-error-100)', '--color-error-border': 'var(--color-error-200)' } }`.
|
|
860
|
+
- **Alert's status icon is drawn at stroke 2.5** (was lucide's 2), in both modes, so it holds its weight beside the text.
|
|
861
|
+
- **Dark materials show glass again** (D137). Dark sheets, menus and modals were 98% opaque, because faint text had to hold 4.5:1 over a white backdrop. The dark backdrop filter now also dims what is behind the glass: new token `--material-backdrop-brightness` (`1` in light, an identity; `0.6` in dark), and the three `--material-filter-*` tokens end in `brightness(var(--material-backdrop-brightness))`. The brightest thing behind dark glass is therefore near-white times 0.6, and the contrast guard measures the fill over that dimmed backdrop (material pairs only; every fallback that removes the filter also makes the fill opaque, so the rule is what a browser paints). Dark alphas re-solved on it: `--material-alpha-thin` 66% to 60%, `--material-alpha-regular` (bars) 76% to 68%, `--material-alpha-thick` 98% to 72% (floors 30, 47 and 68). No text role moved and the thick material still carries all five. Restore the 8.1-like opaque dark overlays: `overrides: { dark: { '--material-alpha-thick': '98%', '--material-backdrop-brightness': '1' } }`, or `data-materials="opaque"`.
|
|
862
|
+
- **`createTheme` now checks the material pairs** (D138). The generator's data did not carry `--material-alpha-*`, so every "text on a material" pair was skipped without a word: a theme reported "all 278 role pairs pass" and the same input now checks 354. An app whose text or surface overrides make glass unreadable over the worst backdrop gets a warning it did not get before; the fix is the alpha of that level for that mode, for example `overrides: { light: { '--material-alpha-thin': '68%' } }`. `--material-alpha-*` and `--material-backdrop-brightness` are honoured per mode in the check.
|
|
863
|
+
- **Light `--material-alpha-thick` is 88%** (was 86%), found by that check: with a strongly tinted neutral (`neutralChroma: 0.08`) the caption on a light sheet over a black backdrop was 4.47:1 at 86%. Neutral hues at chroma 0 to 0.1 now pass. Restore: `overrides: { light: { '--material-alpha-thick': '86%' } }`.
|
|
864
|
+
- **The dark half of the surface elevation guard is a new rule** (D134), because the old one (four levels, each 5 to 8 lightness points above the last) described the old design. Now: every dark surface role is declared as one step of the neutral ramp, not a mix; lightness never falls from page to overlay and the card is lighter than the page; at most two tone steps above the page, each 2 to 5 OKLCH L points, the top within 8; the overlay rim reaches 1.5:1 against the page and the card and 1.3:1 against the overlay; the card edge reaches 1.2:1 against the page and the card, as in light. `tokens/surface-ladder.js` exports `SURFACE_STEP` in place of the wash alphas.
|
|
865
|
+
- **The field edge is guarded on the elevated and overlay surfaces too**, in both modes (`--color-input-border` at 3:1; a field in a sheet or a modal). Two more guarded pairs in `createTheme`'s check.
|
|
866
|
+
|
|
867
|
+
### Fixed
|
|
868
|
+
|
|
869
|
+
- **`cn` knows the named spacing steps** (Z-064): `xs`, `sm`, `md`, `lg`, `xl` and `2xl` (`--spacing-*`) are registered with the class merger as spacing values, so a call-site `p-sm` beats a component's `p-xs`, and the same for margin, gap, inset and the other spacing groups. Until now `p-xs p-sm` kept both classes and the stylesheet order picked the padding. `3xl` is not registered: the library has no `--spacing-3xl`.
|
|
870
|
+
- **`createTheme`: an ink role overridden in one mode keeps the ramp's ink in the other** (Z-061). With `neutralChroma`, a role such as `--color-action-secondary` set under `overrides.light` alone got the library's grey `rgba(250, 250, 250, 0.09)` under the dark selectors; it now gets the tinted `color-mix(in srgb, var(--zabi-base-50) 9%, transparent)` that the option derives, as it does without the override. The same holds the other way: a role set under `overrides.dark` alone keeps the tinted mix in light (it fell back to the library's grey there). Roles: `--color-action-secondary` (and `-hover`, `-active`), `--color-surface-hover`, `--color-surface-active`, `--color-border-overlay`, `--shadow-color`. An app that restated the dark mixes as a workaround can delete them; to keep the old grey in the other mode, set it there.
|
|
871
|
+
|
|
872
|
+
## [9.0.0-alpha.3] - 2026-10-06
|
|
873
|
+
|
|
874
|
+
A third local preview build of the 9.0 visual direction, not published to npm;
|
|
875
|
+
8.1.0 stays the stable version. It is the redesign of the shell: AppBar,
|
|
876
|
+
BottomTabBar, StickyActionBar and TopNavbar are flush with the page and turn to
|
|
877
|
+
glass once content scrolls under them; the AppShell footer lies over the
|
|
878
|
+
scroller; the active tab loses its outline; the sidebar and the top navbar have
|
|
879
|
+
one active indicator, sentence-case headings and tighter rows; AppBar gains a
|
|
880
|
+
large title that condenses and a brand tone; IconButton gains a count; and
|
|
881
|
+
AppShell gains a `navigation` slot that is tabs, a rail or a sidebar by
|
|
882
|
+
breakpoint, with `AppNavigation` as its ready-made content. No token, prop,
|
|
883
|
+
variant or export is removed or renamed; several defaults change what a consumer
|
|
884
|
+
sees, and each has a line that restores 8.1 where one exists. Built quickly and
|
|
885
|
+
tested lightly on purpose: the type and design checks, the unit run, the theme
|
|
886
|
+
tests, the package build and a scratch install were run on it, and the new
|
|
887
|
+
shell stories were looked at once in Storybook (light and dark, at phone, tablet
|
|
888
|
+
and desktop widths, at rest and scrolled). No browser suite was run (the
|
|
889
|
+
library's Playwright specs will need updating for the new looks), there was no
|
|
890
|
+
accessibility or QA review, and nothing was tried on a real phone, in Safari or
|
|
891
|
+
Firefox, at 200% text or with a keyboard. Dark mode is still not art-directed.
|
|
892
|
+
|
|
893
|
+
### Added
|
|
894
|
+
|
|
895
|
+
- **Bar tokens** (Z-045; D124): `--color-bar` (a bar's fill at rest, default `var(--color-surface-chrome)`), `--color-bar-border` (the hairline a bar shows once content is under it: the headline colour at 10%), `--color-bar-brand` (the fill of an on-brand AppBar, default `var(--color-action-primary)`) and `--color-tabbar-active` (the active tab's pill, default `var(--color-nav-menu-active)`; Z-046). All four are restated in dark and settable through `createTheme` `overrides` per mode. Classes `bg-tabbar-active`, `bg-bar-brand`, `border-bar`. Guarded pairs: the active tab label on its pill, on-brand text on the brand bar, the headline on a bar at rest.
|
|
896
|
+
- **`.material-bar`**: the surface of a bar. A `::before` layer paints `--color-bar` at rest and, when the element has `data-scrolled-under="true"`, the regular material with a 1px `--color-bar-border` hairline on the content side (`data-bar-edge="bottom"` puts it on the top edge). The element itself has no `backdrop-filter`, so a Dropdown or Tooltip in a bar is still placed against the screen. Under the opaque fallbacks a scrolled bar is the page colour with the hairline.
|
|
897
|
+
- **`.material-layer-regular`**: the layer form of the regular material, as `.material-layer-thick` is for the thick one, for a floating bar that holds controls.
|
|
898
|
+
|
|
899
|
+
- **`BottomTabBarItem.activeIcon`** (Z-046): drawn instead of `icon` on the active tab, typically the filled form. Without it the same icon is drawn with a heavier stroke (2.5) when active; `BottomTabBarIcon` now also accepts `strokeWidth`.
|
|
900
|
+
- **`BottomTabBar floating`** (D126): the bar as a rounded glass capsule inset 12px from the screen edges, always on the regular material. Default `false`.
|
|
901
|
+
- **AppShell `navigation` slot** (D101-S, D130): `navigation?: Snippet<[{ placement: "tabs" | "rail" | "sidebar" }]>` and `navigationPlacement?: "auto" | "tabs" | "rail" | "sidebar"` (default `"auto"`). One navigation, rendered once and first in the DOM, placed by CSS: tabs over the bottom of the screen below 48rem (768px), a rail from 48rem and a sidebar from 64rem (1024px) as a full-height column at the inline start; a forced value applies at every width. The server render is right without scripts. The host carries `data-navigation-placement`; `--app-shell-bottom-inset` counts the tabs (and a `footer`, which then sits directly above them); new `--app-shell-start-inset` is the rail's or sidebar's width (`0px` as tabs), mirrored on `<html>` with the other two. `footer`, `header` and `children` work as before, and a shell without `navigation` renders the markup it always did. Types `AppShellNavigationPlacement`, `AppShellNavigationMode`, `AppShellNavigationContext` and the constants `APP_SHELL_RAIL_MIN_WIDTH`, `APP_SHELL_SIDEBAR_MIN_WIDTH`.
|
|
902
|
+
- **`AppNavigation`** (new organism; D130): the ready-made content for the slot. Takes the `BottomTabBar` item list (`items`, `active`, `label`, `badgeLabel`, `badgeMax`, `floating`) and draws it as a BottomTabBar, an 80px rail (icon in the tab pill over a label) or a 256px sidebar (36px rows, icon before the label, count at the end, one tinted active indicator), by CSS alone; `header` and `footer` snippets go above and below the list in the side forms. Outside a shell it is the tab bar.
|
|
903
|
+
- **Stories**: `Design System/Organisms/AppShell (9.0)` shows the redesigned shell as a phone, a phone with floating tabs, a tablet rail, a desktop sidebar, adaptive, and with a brand bar. The prototype story stays beside it.
|
|
904
|
+
- **`AppBar largeTitle`** (Z-045; D128): the title large (30px bold, capped at 39px, two lines at most) on a second row under the 56px bar. The large row scrolls away with the content, with no scroll-linked animation; the bar stays and shows the title small once the large one has gone under it. The heading element is the large title; the small one is hidden from assistive technology. In an AppShell, `--app-shell-top-inset` is the condensed height. `collapseOnScroll` is ignored with it (a dev build warns). Before the bar is measured, and without scripts, the whole header sticks with the large title in view.
|
|
905
|
+
- **`AppBar tone`** (Z-045; D129): `"default"`, `"transparent"` (no fill at rest, glass once content is under it: for a canvas, an image or a colour block behind the bar) or `"brand"` (`--color-bar-brand` with on-brand text and controls, opaque at rest and scrolled, no hairline, so a brand block directly under the bar joins it). The header carries `data-tone`.
|
|
906
|
+
- **`AppBar position`** (Z-067): `"sticky"` (default) or `"static"` for a bar in the flow of the page, inside a block of the content: it scrolls away with the block and is never glass. `class="static"`, the 8.1 way to say this, is read as `position="static"` (the bar is `relative`, because the material's layer is placed against it).
|
|
907
|
+
- **`IconButton count`, `countLabel`, `countMax`** (Z-056 part 1; D131): a count in the attention colour drawn inside the button's own box, in its upper end corner, so a bar or an `overflow` ancestor cannot cut it. `99+` above `countMax` (default 99); a dot at `xs` and `sm`; nothing for 0 or while loading. The accessible name is the `label`, a comma and `countLabel(count)` (default "3 new").
|
|
908
|
+
- **`scrollEdge`** on AppBar, BottomTabBar, StickyActionBar and TopNavbar (D123): `"auto"` (default: flush at rest, glass and a hairline once content is under the bar), `"always"` or `"never"`. Each bar writes `data-scrolled-under="true|false"` on its root (`"false"` on the server).
|
|
909
|
+
- **`AppShell canvas`** (D125): paints the shell with `bg-canvas` (the page colour with the brand wash) and lets the header bar rest transparent over it. Default `false`.
|
|
910
|
+
- **AppShell scroll state**: `data-scrolled-top` and `data-scrolled-bottom` on the host, and `--app-shell-footer-overlay` on the scroller (the bottom inset when a footer lies over it, else `0px`). `getAppShell()` in `util/app-shell` gives a component inside a shell the same state; `util/scroll-edge` (`watchScrollEdge`) is the helper for a bar on its own.
|
|
911
|
+
|
|
912
|
+
### Changed (visible in 9.0)
|
|
913
|
+
|
|
914
|
+
- **Bars are flush with the page and turn to glass when content scrolls under them** (Z-045; D108, D123, D124). AppBar, BottomTabBar, StickyActionBar and TopNavbar are the page colour with no line at rest; once content is under one it takes the regular material and a 1px hairline on the content side. They no longer have a border of their own, so AppBar is 56px (was 57) and BottomTabBar 64px (was 65); an app that hard-coded 57px or 65px should read `--app-shell-top-inset` / `--app-shell-bottom-inset` or use the new numbers. Restore 8.1's look: `scrollEdge="always"` on the bar together with `data-materials="opaque"` on `<html>` and `--color-surface-chrome: var(--color-surface-raised)` (a permanent opaque raised bar with its line).
|
|
915
|
+
- **BottomTabBar: the active tab has no outline** (Z-046; D107, D126). It is marked by the tinted pill (`--color-tabbar-active`), a filled or heavier icon, a semibold label and the active colour; the focus ring shows on `:focus-visible` only. Under forced colours the pill keeps a system-colour outline. The count badge is the attention colour (the solid error fill) with a 2px ring in the bar colour. Restore 8.1: none by token (the outline was the reported defect); `--color-tabbar-active` takes any fill.
|
|
916
|
+
- **Sidebar and TopNavbar restyled** (D127). SidebarNavigation: one active indicator, the tinted fill with the active text colour and a semibold label (the 3px leading bar is gone; forced colours keep an outline); rows are 36px at 14px with 20px icons and 44px on coarse pointers; sections are separated by space, not lines. SidebarNavSection headings are sentence case as written (no `uppercase`, no tracking), 12px medium. SidebarShell reads `--color-surface-chrome` (the page colour) with a weak hairline on the content side and no shadow in the `card` layout. SidebarPanel is a flat card (no shadow or ring; `variant="elevated"` now looks the same as the default) and its selected row uses the same single indicator. TopNavbar links are 14px without tracking, medium, semibold when active. Restore 8.1: none by token or prop.
|
|
917
|
+
- **A `bg-*` or `border-*` class on a bar no longer changes its surface.** The fill and the hairline are painted on the material's layer, not on the bar element, so `class="bg-transparent border-b-0"` on an AppBar (or a `bg-*` on BottomTabBar, StickyActionBar, TopNavbar) has no visible effect. Use `tone="transparent"` or `tone="brand"` on AppBar, or set `--color-bar` on the bar (`class="[--color-bar:transparent]"`, any colour) and `scrollEdge="never"` for no glass and no line.
|
|
918
|
+
- **The AppShell footer lies over the scroller** (D125) instead of taking a row of its own, so content passes under the tab bar. `--app-shell-bottom-inset` has the same meaning and value; the shell now gives the content that much bottom padding and the scroller that much `scroll-padding-bottom`. A floating button at `bottom: calc(var(--app-shell-bottom-inset) + 1rem)` sits where it did. Content that sized itself to the scroller's height (`min-h-full`) is now as tall as the screen minus the header only; its end is still above the footer because of the padding. No restore short of reverting.
|
|
919
|
+
- **StickyActionBar and UnsavedChangesBar in a shell with a footer** stick above the footer (they read `--app-shell-footer-overlay`). **UnsavedChangesBar** is the regular material (rim, highlight, soft shadow) instead of an opaque card with a border and `shadow-lg`; `data-materials="opaque"` makes it opaque.
|
|
920
|
+
- **`--color-surface-chrome` is the page colour** (`var(--color-surface-base)`) in both modes, not the raised surface (D108), so bars and sidebars that read it are flush with the page. The regular material is mixed from it and was re-solved: light 84% (was 80%), dark 76% (unchanged); thin stays 58% / 66%. Restore 8.1: `--color-surface-chrome: var(--color-surface-raised)` (per mode in `overrides`).
|
|
921
|
+
|
|
922
|
+
## [9.0.0-alpha.2] - 2026-10-06
|
|
923
|
+
|
|
924
|
+
A second local preview build of the 9.0 visual direction, not published to npm;
|
|
925
|
+
8.1.0 stays the stable version. It adds the first restyled components on top of
|
|
926
|
+
alpha.1's token layer: List and Badge without their double outlines, a sliding
|
|
927
|
+
indicator in Tabs and SegmentedControl, gradient solid buttons with new `tonal`
|
|
928
|
+
and `text` variants and a 56px size, a lighter off Toggle, the seven overlays
|
|
929
|
+
on the thick material and a spring for BottomSheet. Bars, the shell and dark
|
|
930
|
+
mode are not touched yet. No token, prop, variant or export is removed or
|
|
931
|
+
renamed; several defaults change what a consumer sees, and each has a line that
|
|
932
|
+
restores 8.1 where one exists. Built quickly and tested lightly on purpose: the
|
|
933
|
+
type and design checks, the unit run, the theme tests, the package build and a
|
|
934
|
+
scratch install were run on it. NOTHING was rendered: no browser suite, no
|
|
935
|
+
Storybook or visual check in either mode, no accessibility or QA review, so the
|
|
936
|
+
glass, the sliding indicators and the springs have never been seen. Using it is
|
|
937
|
+
the test.
|
|
938
|
+
|
|
939
|
+
### Added
|
|
940
|
+
|
|
941
|
+
- **Button `variant="tonal"`** (Z-047): the tonal fill with its brand label, flat, for secondary actions and chosen answers. IconButton has it too.
|
|
942
|
+
- **Button `variant="text"`** (Z-048): a standalone text action in the link colour (the on-colour inside `.on-brand` and `.on-accent`): no underline, a semibold label, the box and 44px touch target of the other variants, `px-2`, a hover and pressed tint. With `href` it is still a boxed action; `variant="link"` with `href` stays the underlined link for a sentence. IconButton shows it as its link look.
|
|
943
|
+
- **Button `size="xl"`** (Z-047): 56px. Button only; `SizeVariant` is unchanged. New type `ButtonSize` (`SizeVariant | "xl"`), exported with `ButtonVariant`. `scripts/check-control-geometry.js` holds it to the same one-line rule as the other sizes.
|
|
944
|
+
- **`--zabi-button-radius` and `--zabi-button-font-weight`** (Z-047) are now read by Button (and the inline link), through `rounded-button`, `font-button` and `font-button-strong`. Defaults are today's corner and a 500 label (600 for `text`).
|
|
945
|
+
- **Control gradient, darken-only in light** (design review: "controls are flat"). `--gradient-control-top-strength` is 0% in light (the fill at the top is the control's own colour, so the label keeps its full contrast and an app's brand colour is not altered) and `--gradient-control-bottom-strength` 14% of `--zabi-base-950` (was 4% / 6%); dark stays 22% / 6%. The veil is for primary, accent and danger: `bg-control-gradient` serves danger too, and danger's label is now a guarded gradient pair. New `--gradient-control-accent-top-strength` and `--gradient-control-accent-bottom-strength` (default: the shared strengths) so one control can differ. The highlight now uses Tailwind's inset-shadow variable (`--tw-inset-shadow`) instead of `--tw-shadow`, so a `shadow-*` class on the same element draws as well. `createTheme` flips a veil to lighten-only in a mode where a dark label would fail the darkened bottom (a light accent), writes 0% if no strength passes, and never moves a strength you set. Restore: `--gradient-control-top-strength: 4%; --gradient-control-bottom-strength: 6%` in light.
|
|
946
|
+
- **Tonal action role** (Z-047): `--color-action-tonal`, `--color-action-tonal-hover`, `--color-action-tonal-active`, `--color-action-tonal-text`; classes `bg-action-tonal` (with its hover and pressed states), `text-action-tonal`. Light is the brand-100 tint with a brand-800 label (8.15, 6.94 and 6.25:1 on rest, hover and pressed); dark the deep tint with the light label (9.48, 6.94, 5.86). `.bg-action-tonal` is a surface inside `.on-brand` and `.on-accent`.
|
|
947
|
+
- **Outline edge, guarded at 3:1** (Z-047): `--color-action-outline-border` and `--color-action-outline-border-hover` (the field edge, 4.63:1 on the light page, 4.83:1 on a card; dark 3.67 and 3.12), classes `border-action-outline` and `hover:border-action-outline-hover`; the block's label colour inside `.on-brand` and `.on-accent`. Restore the old edge: `--color-action-outline-border: var(--color-border); --color-action-outline-border-hover: var(--color-border-medium)`.
|
|
948
|
+
- **Button corner and label weight, apart from fields** (Z-047): `rounded-button` (`--zabi-button-radius`, falling back to `--radius-control`), `font-button` (`--zabi-button-font-weight`, falling back to 500) and `font-button-strong` (600, for the text variant). Declared nowhere, so set them on any ancestor or in `createTheme` `overrides`; a scoped `--radius-control` still flows. Defaults keep today's look.
|
|
949
|
+
- **Toggle off track** that does not read as disabled: `--color-toggle-track` (light base-200, dark base-300), `-hover`, `-active`, and the edge `--color-toggle-track-border` (the field edge: 4.63:1 on the light page, 4.83:1 on a card, dark 3.67 and 3.12). Classes `bg-toggle-track` (with hover and pressed) and `ring-toggle-track` (an inset 1px edge that does not change the switch's size). `--color-control-track*` is untouched; Slider and Progress look as before. Restore: `--color-toggle-track: var(--color-control-track); --color-toggle-track-hover: var(--color-control-track-hover); --color-toggle-track-active: var(--color-control-track-active); --color-toggle-track-border: transparent`.
|
|
950
|
+
- **`SegmentedControl tone`** (Z-052): `"neutral"` (default) or `"primary"`. Neutral draws the selected segment as a thumb: card colour, headline-coloured label, `--shadow-sm` and a 1px edge, on the tinted track. New tokens `--color-segment-thumb` (light: the raised surface; dark: the overlay surface), `--color-segment-thumb-text` and `--color-segment-thumb-border` (the control boundary, held to 3:1 against the track over the page and over a card, with the thumb text held to 4.5:1 on the thumb).
|
|
951
|
+
- **A sliding indicator in Tabs and SegmentedControl.** After mount the selected item's mark (the underline, the pill, the thumb or the primary fill) is one element that moves and resizes to the selected item over `--duration-moderate`: with `--ease-spring` for the pill and the thumb, `--ease-standard` for the underline. It is measured, so it follows resizes, zoomed text (a wrapped SegmentedControl moves the thumb in two dimensions), right-to-left layouts and a scrolling tablist. Server-rendered, before hydration, without ResizeObserver or layout, and under forced colours the selected item draws its own mark as before. No motion under reduced motion.
|
|
952
|
+
- **`Badge variant="brand"`** (Z-056 part 3): the primary brand colour as a badge, subtle (brand tint, link-coloured text) and solid (the primary fill with its label). New token `--color-action-primary-border` (the brand tint's edge), and the guarded pairs `badge subtle · brand` and `badge solid · brand`.
|
|
953
|
+
- **`Badge bordered`** (Z-056 part 2): puts a subtle badge's family edge back. Default `false`.
|
|
954
|
+
- **`--zabi-list-row-border-color`** (default `transparent`) and **`--zabi-list-divider-color`** (default `var(--color-border)`): set on a list or any ancestor.
|
|
955
|
+
- **`.material-layer-thick`** (package 10): the thick material in a form for a container that holds fixed-position content. The fill, backdrop filter, highlight and rim are painted on a `::before` layer behind the content (isolated, `z-index: -1`, same tokens as `.material-thick`, so every fallback applies); the element itself carries only the outer shadow, so it is not the containing block of its `position: fixed` descendants. The element must be positioned and must not scroll its own content. Use it for any panel that can hold a popover; use `.material-thick` for a leaf.
|
|
956
|
+
- **`Tooltip tone`**: `"material"` (default) or `"inverted"`. Typed in `TooltipProps`, documented in the catalog and the story.
|
|
957
|
+
- **Guarded pairs on the thick material**: a Toast's status text (`--color-success-text`, `--color-error-text`, `--color-warning-text`, 4.5:1), the status colours as icons (3:1) and `--color-link` (4.5:1), each over the worst backdrop and over the page, in both modes.
|
|
958
|
+
|
|
959
|
+
### Changed (visible in 9.0)
|
|
960
|
+
|
|
961
|
+
- **A soft gradient on primary, accent and danger Buttons and IconButtons, the FloatingActionButton and the on Toggle** (design review: "controls are flat"): a darker bottom and a 1px top highlight over the control's own fill. None when disabled, loading, or a toggled-on IconButton. Restore 8.1: `--gradient-control-bottom-strength: 0%; --shadow-control-highlight: none` (and `--gradient-control-accent-bottom-strength: 0%` if the accent strength was set apart).
|
|
962
|
+
- **The outline Button and IconButton have a 3:1 edge** (Z-047), not the 1.25:1 card edge. Restore 8.1: `--color-action-outline-border: var(--color-border); --color-action-outline-border-hover: var(--color-border-medium)`.
|
|
963
|
+
- **The off Toggle is a light tint with a 1px edge**, no longer the mid-grey that read as disabled; its colour change and the thumb's slide use the motion tokens (`--duration-moderate`, the thumb on the spring easing). Restore 8.1: `--color-toggle-track: var(--color-control-track); --color-toggle-track-hover: var(--color-control-track-hover); --color-toggle-track-active: var(--color-control-track-active); --color-toggle-track-border: transparent`.
|
|
964
|
+
- Button, IconButton and the FloatingActionButton change colour over `--duration-base` instead of a literal 150ms, so reduced motion zeroes it.
|
|
965
|
+
- **`createTheme`: links follow an overridden primary** (Z-048). An app that sets `--color-action-primary` in `overrides` (and not `--color-link`) gets `--color-link` equal to it, and `--color-link-hover` equal to the primary hover if it set one, in each mode the override applies to, as long as no guarded pair fails that did not before; otherwise the ramp's link stays in that mode. An explicit `--color-link` always wins. The library default stays `--color-link: var(--color-brand-700)`. The generator also resolves the control veil pairs now (it skipped them before). Restore: `overrides: { '--color-link': 'var(--color-brand-700)', '--color-link-hover': 'var(--color-brand-800)' }`.
|
|
966
|
+
- **SegmentedControl: the selected segment is a neutral thumb, not a solid primary fill** (Z-052), so the control is no longer louder than the screen's main button. Restore 8.1: `tone="primary"`.
|
|
967
|
+
- **The indicators of Tabs and SegmentedControl slide** between items instead of switching. Restore 8.1: `--duration-moderate: 0s` on the component or an ancestor makes the move instant (the token is shared with other components); reduced motion already has none.
|
|
968
|
+
- **List rows have no border of their own; a hairline divides them.** A row inside the bordered `.list-group` shell (or a bare `<List>`) drew a second outline; now rows keep a transparent 1px border (so height and text do not move) and a 1px `--color-border` divider sits in the gap between rows, none above the first or below the last, hidden next to a selected row (which keeps its fill and `border-action-primary` edge). The `.list-group` shell keeps its one border. Row transitions use the motion tokens. Restore 8.1: `--zabi-list-row-border-color: var(--color-border); --zabi-list-divider-color: transparent;` on the list or any ancestor.
|
|
969
|
+
- **A subtle Badge has no visible border.** The 1px border box stays (same height), drawn transparent. Restore 8.1: `bordered` on the Badge. Solid badges are unchanged.
|
|
970
|
+
- **BottomSheet arrives on a spring.** The slide in, the move between snap points and the settle after a drag use `--ease-spring` over `--duration-slow` (300ms); they were a literal 200ms ease-out. The slide in reads both tokens from the sheet, so a theme that sets them is followed, and falls back to `ease-out` where the tokens cannot be read or the engine rejects a `linear()` easing. A 2rem filler under the panel's bottom edge keeps the overshoot from showing the page. Reduced motion is unchanged (no slide, no transition), and so are drag, snap logic, focus and scroll lock. Restore 8.1: on the sheet or an ancestor, `--ease-spring: var(--ease-out); --duration-slow: 200ms` (both tokens are shared with other components), or `class="duration-200 ease-out"` on the `BottomSheet`.
|
|
971
|
+
- **Overlays are frosted glass** (package 10): BottomSheet, Drawer, Modal, the Dropdown menu, the Select list, the Tooltip bubble and every Toast (and Toaster toast) take the thick material instead of an opaque overlay fill with a border and `shadow-lg`; the material brings its own rim and shadow. Dividers inside a header or footer, row hover and selected fills, and the scrims behind Modal, Drawer and BottomSheet are unchanged. Dark thick is 98% opaque by design for now, so a dark overlay shows almost no glass. In Modal, Drawer and BottomSheet the glass is a layer behind the content: the Drawer panel lost its edge border and the BottomSheet its top and side borders (the rim replaces them), and a Modal's panel no longer scrolls itself: its Card does (`overflow-y-auto` moved from the panel to the Card, `min-h-0` added), and the Card takes the panel's full corner radius. A fixed-position popover inside any of them is placed against the viewport exactly as before, with materials on or off. Restore 8.1: `data-materials="opaque"` on the root or any ancestor (a portaled overlay lives under `body`, so put it on `html` or `body`) gives opaque overlay surfaces. The 8.1 `shadow-lg` and 1px border look itself is not restorable with one token.
|
|
972
|
+
- **The Tooltip bubble is glass with body text and no arrow by default.** The arrow is a second shape on a translucent fill and would show a seam. Restore 8.1: `tone="inverted"` (the dark bubble with its arrow, still reading `--color-tooltip-bg` and `--color-tooltip-fg`).
|
|
973
|
+
- **Toast text uses the status `-text` colour** (`text-success-text`, `text-error-text`, `text-warning-text`) instead of the plain status colour, which is 3.7:1 to 3.9:1 on glass; the border stays the plain colour. The neutral toast draws no second border. A Dropdown danger item uses `text-error-text` for the same reason. Restore 8.1: none by token; the colours are slightly darker in light and lighter in dark.
|
|
974
|
+
- **Overlay motion reads the tokens**: Dropdown fades over `--duration-moderate` with `--ease-standard`, the Modal's slide over `--duration-slow` with `--ease-out`, the Drawer reads `--duration-moderate` and `--ease-out` for its slide, the Select over `--duration-base` and `--duration-moderate`, the Tooltip over `--duration-moderate`. Reduced motion zeroes them.
|
|
975
|
+
|
|
976
|
+
### Fixed
|
|
977
|
+
|
|
978
|
+
- **Z-040: the pressed field follows the field.** `--color-input-active` (the
|
|
979
|
+
pressed Select trigger) is now derived from `--color-input`, so an app that
|
|
980
|
+
sets the light card colour through `createTheme` `overrides.light` no longer
|
|
981
|
+
trips "pressed field against the resting field". Light mixes 12% of the ink
|
|
982
|
+
(`--zabi-base-900`) into the field, dark 10% of the light end
|
|
983
|
+
(`--zabi-base-50`). On the default theme light moves from `#e4e4e7` to
|
|
984
|
+
`#e3e3e4` and dark from `#333338` to `#353538`. An app that overrode
|
|
985
|
+
`--color-input-active` by hand to get past the check can delete that override.
|
|
986
|
+
Restore: `--color-input-active: var(--color-base-200)` in light,
|
|
987
|
+
`var(--color-base-250)` in dark. The neutral chroma at the light end is not a
|
|
988
|
+
new option: set the page per mode (`overrides.light`, `--color-surface-base`),
|
|
989
|
+
see THEMING.md.
|
|
990
|
+
|
|
991
|
+
## [9.0.0-alpha.1] - 2026-10-06
|
|
992
|
+
|
|
993
|
+
A local preview build of the 9.0 visual direction, not published to npm; 8.1.0
|
|
994
|
+
stays the stable version. It holds the token layer only (surfaces, status
|
|
995
|
+
colours, materials, gradients, motion, typeface): no component is restyled yet
|
|
996
|
+
except the default `Card` and the `ImageUpload` hover plate, and dark mode is
|
|
997
|
+
not yet art-directed. No token, prop or export is removed or renamed; values
|
|
998
|
+
move, and each visible change below has a line that restores 8.1. Built quickly
|
|
999
|
+
and tested lightly on purpose: the type and design checks, one unit run, the
|
|
1000
|
+
theme tests, the package build and a scratch install were run on it. NOTHING
|
|
1001
|
+
was rendered: no browser suite, no Storybook or visual check in either mode, no
|
|
1002
|
+
accessibility or QA review. Using it is the test.
|
|
1003
|
+
|
|
1004
|
+
### Added
|
|
1005
|
+
|
|
1006
|
+
- **Motion tokens.** `--duration-fast|base|moderate|slow` (100, 150, 200, 300ms) and `--ease-standard` (`cubic-bezier(0.2, 0, 0, 1)`), `--ease-out` (Tailwind's own curve) and `--ease-spring` (a lightly damped `linear()` spring with one overshoot, with a cubic-bezier fallback where `linear()` is unsupported). Utilities: `ease-standard`, `ease-out`, `ease-spring`; durations are `duration-(--duration-moderate)` (Tailwind has no `duration-fast` style name for custom properties). Under `prefers-reduced-motion: reduce` the four durations become `0s` and the spring becomes the standard curve, in both modes and over an app's own values (`:root:root`, like the materials fallback), so everything that reads the tokens follows. All are settable through `createTheme` `overrides`.
|
|
1007
|
+
- **Materials (frosted glass) for what floats over content.** Tokens `--color-material-thin|regular|thick` (the fills: `--color-surface-chrome` at an alpha for thin and regular, `--color-surface-overlay` for thick), `--material-alpha-thin|regular|thick`, `--material-blur-thin|regular|thick`, `--material-saturate`, `--material-filter-*`, `--color-material-highlight`, `--color-material-rim`, `--shadow-material` and `--shadow-opacity-contact`, and the classes `material-thin`, `material-regular` and `material-thick` (fill, backdrop filter, edge and shadow; they compose with `rounded-*` and `focus-ring`). Highlight, rim and shadow derive from the neutral ramp, so `neutralChroma` tints them. No component uses them yet except the `ImageUpload` hover plate.
|
|
1008
|
+
- **Alpha floors in the contrast guard.** Each material is composited over the worst backdrop of the mode (`--zabi-base-950` behind light, `--zabi-base-50` behind dark) and over the page: thin must give its headline 4.5:1, regular its headline, body and label, thick all five text roles, and regular and thick the focus ring 3:1 (a thin control's ring is drawn outside it). Shipped alphas for thin, regular and thick: light 58, 80 and 86, dark 66, 76 and 98, each the smallest whole 2% that passes except light thin, whose floor is lower. Dark thick is close to opaque because captions must hold over a white backdrop; dark is art-directed in a later 9.0 change. `scripts/contrast-pairs.js` has a `behind` form that `createTheme` evaluates too, so an app's generated theme gets the same floor; every material token is settable through `overrides` (`light`, `dark`, `both`).
|
|
1009
|
+
- **Fallbacks and `data-materials="opaque"`.** The fills become the opaque surface they are made from, and the blur goes, under `prefers-reduced-transparency: reduce`, `prefers-contrast: more`, `forced-colors: active`, where `backdrop-filter` is unsupported, and where the root or any ancestor has `data-materials="opaque"`. Under forced colors the rim is a `CanvasText` outline. The fallbacks are in the theme files and in `zabi-components-colors.css`.
|
|
1010
|
+
- **Gradient tokens and three classes.** Canvas: `--gradient-canvas` (two soft radial washes from brand 300 and accent 300, a `background-image` only), its stops `--color-canvas-wash-brand` and `--color-canvas-wash-accent`, and `--gradient-canvas-strength` (light 55%, dark 25%); the class `bg-canvas` adds `--color-surface-base` under it. The library does not paint it on `body`. Controls: `--gradient-control` and `--gradient-control-accent`, a translucent veil over the control's own `background-color` (so hover and pressed fills still show), with the veil colours `--color-control-gradient-start|end` and `--color-control-gradient-accent-start|end`, strengths `--gradient-control-top-strength` and `--gradient-control-bottom-strength`, and `--shadow-control-highlight` (an inset 1px top edge); classes `bg-control-gradient` and `bg-control-gradient-accent` (the highlight goes through Tailwind's shadow variables, so `focus-ring` still draws). All follow a custom `brand` and `accent` and are settable through `createTheme` `overrides` (`light`, `dark`, `both`). No component uses them yet.
|
|
1011
|
+
- **Worst-case rule for gradients.** Contrast is never measured against a gradient. The guard holds the five text roles, `--color-link` (4.5:1), `--color-focus-ring` and `--color-input-border` (3:1) on each canvas wash stop over `--color-surface-base`, and the solid-button label on both veil ends over rest, hover and pressed fills (4.5:1). Solved strengths: canvas light 55 and dark 25 (the prototype used 85 and 45); top veil light 4 and dark 22. `createTheme` evaluates the same pairs.
|
|
1012
|
+
- `scripts/check-token-violations.js` fails `backdrop-blur-*` and `backdrop-filter` in `src/components`. The `ImageUpload` hover plate (`bg-surface-overlay/60 backdrop-blur-md`) is now `material-thin`.
|
|
1013
|
+
|
|
1014
|
+
### Changed (visible in 9.0)
|
|
1015
|
+
|
|
1016
|
+
- **Default typeface is the platform's UI face.** `--font-family-sans` is `ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` plus the emoji faces; `--font-family-heading` still follows it; `--font-family-mono` is `ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace` (it was Monaco, Menlo, Ubuntu Mono). The library and its Storybook no longer load Nunito Sans, so a default install downloads no font. The face now differs by platform (San Francisco, Segoe UI, Roboto) and text widths change, so truncation and wrapping can move. Restore 8.1: load Nunito Sans yourself and set `--font-family-sans: 'Nunito Sans', ui-sans-serif, system-ui, sans-serif` on `:root` after the imports. The docs site keeps Familjen Grotesk for its marketing headings.
|
|
1017
|
+
- **Default transition curve.** `transition`, `transition-colors`, `transition-opacity` and the rest, where a component sets no duration of its own, now use `--ease-standard` instead of Tailwind's `cubic-bezier(0.4, 0, 0.2, 1)`; the duration stays 150ms (`--duration-base`). Restore Tailwind's curve: `--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)` on `:root` after the imports, or `createTheme({ …, overrides: { '--default-transition-timing-function': 'cubic-bezier(0.4, 0, 0.2, 1)' } })`.
|
|
1018
|
+
- **Light page and cards.** The light page is now near-white (`--color-surface-base` is `--zabi-base-50`, `#fafafa`; it was `--zabi-base-150`, `#ececee`), and `--color-page` and `--color-background` follow it. A default `Card` now has a 1px `--color-border` edge and no shadow (it used `shadow-sm`, and `hover:shadow-lg` when clickable); it is the same as `variant="outlined"`, which stays. `elevated` and `flat` are unchanged. A clickable card's hover fill is now `--color-card-hover` = base-150 (was base-100) and its pressed fill base-200, and `--color-input-disabled` is base-150 and `--color-background-secondary` base-100, so they still read against the lighter page. Dark does not change.
|
|
1019
|
+
- **New token `--color-surface-chrome`** (and the `bg-surface-chrome` utility) for bars and sidebars: `var(--color-surface-raised)` in both modes. The components do not read it yet. In `createTheme`, set it per mode with `overrides: { light: { '--color-surface-chrome': … }, dark: { … } }`.
|
|
1020
|
+
- **Restore 8.1.** Page: `createTheme({ …, overrides: { light: { '--color-surface-base': 'var(--zabi-base-150)' } } })`, or `--color-surface-base: var(--zabi-base-150)` in light CSS. Card: there is no `variant` that gives the old shadowed default; pass `class="border-transparent shadow-sm"` to approximate it.
|
|
1021
|
+
- **Status colours.** Every `-subtle` fill moves from ramp step 200 to step 100 and every `-border` from 300 to 200 in light (`success`, `warning`, `error`, `energetic`, `accent`, `info`; `--color-action-primary-subtle` and its hover and pressed steps follow, to 100, 200 and 300/200). Neutral's fill is `--color-base-200` (was 250) and its edge `--color-base-250` (was 300). Dark already used 100 and 200 and is only affected by the regenerated ramps below. The `warning`, `citron` (the default `accent` and `energetic`) and `pine` (`success`) ramps are regenerated on the same lightness curve with new hue and chroma, which is breaking by the repo's policy for every `--zabi-warning-*`, `--zabi-citron-*`, `--zabi-accent-*` and `--zabi-pine-*` step. Step 600: warning `#9e6000` to `#a05f00`, citron and accent `#707400` to `#5d7900`, pine `#00805e` to `#00804b`. Contrast ratios are unchanged to within rounding. An app that passes its own `accent` or `brand` to `createTheme` is unaffected, except that its dark mirror of `success`, `warning` and `energetic` follows the new ramps.
|
|
1022
|
+
- **Restore 8.1 status colours.** Subtle fills: set `--color-<family>-subtle: var(--color-<ramp>-200)` and `--color-<family>-border: var(--color-<ramp>-300)` (ramp `pine` for success, `iris` for info, `citron` for energetic) in light, and `--color-neutral-subtle: var(--color-base-250)`, `--color-neutral-border: var(--color-base-300)`. Ramps: pass the old colour to `createTheme` (`accent: '#707400'`) or set the old `--zabi-*` steps on `:root`; the 8.1 seeds were warning hue 82, shift -24, chroma 0.165; citron hue 112, chroma 0.135; pine hue 170, chroma 0.115.
|
|
1023
|
+
- **Softer, layered shadows.** `--shadow-sm|md|lg|xl` keep their names and are each a tight contact shadow plus a wider soft one. `--shadow-opacity` is now the strength of the wide layer: `0.10` in light (was `0.14`) and `0.24` in dark (was `0.3`); the new `--shadow-opacity-contact` is the contact layer, `0.06` light and `0.3` dark. Restore 8.1: in light `--shadow-opacity: 0.14`, `--shadow-sm: 0 1px 3px 0 rgb(var(--shadow-color) / var(--shadow-opacity)), 0 1px 2px -1px rgb(var(--shadow-color) / var(--shadow-opacity))`, `--shadow-md: 0 4px 6px -1px rgb(var(--shadow-color) / var(--shadow-opacity)), 0 2px 4px -2px rgb(var(--shadow-color) / var(--shadow-opacity))`, `--shadow-lg: 0 10px 15px -3px rgb(var(--shadow-color) / var(--shadow-opacity)), 0 4px 6px -4px rgb(var(--shadow-color) / var(--shadow-opacity))`, `--shadow-xl: 0 20px 25px -5px rgb(var(--shadow-color) / var(--shadow-opacity)), 0 8px 10px -6px rgb(var(--shadow-color) / var(--shadow-opacity))`; in dark the same with `--shadow-opacity: 0.3`.
|
|
1024
|
+
- The surface guard (`scripts/check-surface-elevation.js`) now checks the light surfaces by the card edge (`--color-border` at 1.2:1 or more on the page and the card) instead of a lightness ladder; the dark rules are unchanged.
|
|
1025
|
+
|
|
1026
|
+
## [8.1.0] - 2026-10-06
|
|
1027
|
+
|
|
1028
|
+
8.1.0 adds a theme generator (`zabi-theme`, `createTheme`) and an accent colour,
|
|
1029
|
+
a mobile set (AppShell, AppBar, BottomTabBar, BottomSheet, Drawer, Select as a
|
|
1030
|
+
sheet, safe areas, 44px touch targets) and more than 25 new components. Form fields,
|
|
1031
|
+
focus rings and light-mode surfaces are easier to see, and `strings` and
|
|
1032
|
+
`ZabiStringsProvider` replace every built-in English text. Nothing from 8.0.0
|
|
1033
|
+
was removed or renamed: every `exports` path, prop and token is still there.
|
|
1034
|
+
8.0.1 was never published, so everything in the 8.0.1 section below (valid theme
|
|
1035
|
+
files, the three-line Tailwind setup working, ColorPicker on the server,
|
|
1036
|
+
Progress named) is part of 8.1.0.
|
|
1037
|
+
|
|
1038
|
+
### Upgrade notes
|
|
1039
|
+
|
|
1040
|
+
Coming from 8.0.0, these are visible without a code change. Each bullet says
|
|
1041
|
+
what to do.
|
|
1042
|
+
|
|
1043
|
+
**Appearance**
|
|
1044
|
+
|
|
1045
|
+
- **Form field edges are darker.** `--color-input-border` is `base-500` in
|
|
1046
|
+
light and dark (was `base-350` light, `base-300` dark) and
|
|
1047
|
+
`--color-input-border-hover` is `base-550` in light. The edge is guarded at
|
|
1048
|
+
3:1 against the field fill, the page and the card, in both themes. A theme
|
|
1049
|
+
of yours that overrides the edge can now report a pair below 3:1 in the
|
|
1050
|
+
generator check. The dark field edge on the overlay surface (sheets, modals)
|
|
1051
|
+
is about 2:1 against the panel and is not guarded. Nothing to do unless you
|
|
1052
|
+
override the edge.
|
|
1053
|
+
- **`Progress` uses new tokens.** The fill is `--color-progress-fill` (default
|
|
1054
|
+
`--color-action-primary`) and the track `--color-progress-track` (default
|
|
1055
|
+
`--color-input`); utilities `bg-progress-fill` and `bg-progress-track`. The
|
|
1056
|
+
fill used to be the hard-coded brand step 600, so a bar changes colour in an
|
|
1057
|
+
app that pins a primary action, and in dark. Fill on track is guarded at
|
|
1058
|
+
3:1. Set the two tokens to keep the old colour.
|
|
1059
|
+
- **Light mode has a visible surface ladder, stronger tints and shadows.** See
|
|
1060
|
+
Changed: the page, cards, tinted fills, hover fills, overlay edge and shadow
|
|
1061
|
+
all move. A card is 1.18:1 on the page (was 1.10). Check screenshots; no
|
|
1062
|
+
token was renamed.
|
|
1063
|
+
- **Focus rings are one step darker in light and lighter in dark.**
|
|
1064
|
+
`--color-focus` is `brand-600` in both themes (light was `brand-500` at
|
|
1065
|
+
2.99:1 on the page; dark was `brand-500`, 2.91:1 on the overlay; dark is
|
|
1066
|
+
#92a9ff by default). The muted ring in dark is one step lighter
|
|
1067
|
+
(`--color-focus-ring-muted`, `base-600`).
|
|
1068
|
+
- **The computed `box-shadow` of a focused element lists five shadows**, not
|
|
1069
|
+
two, because the ring now composes with shadow utilities. A test that reads
|
|
1070
|
+
the ring colour from it should pick the 4px spread.
|
|
1071
|
+
- **Dark surfaces are `color-mix()` expressions** over `--zabi-base-*`
|
|
1072
|
+
(`--color-surface-raised`, `-elevated`, `-overlay`). The pixels are the same;
|
|
1073
|
+
`getComputedStyle` reports `color(srgb …)`, not `rgb(…)`, so a test that
|
|
1074
|
+
compares the string needs updating.
|
|
1075
|
+
- **Placeholder text is one step stronger** in both themes
|
|
1076
|
+
(`--color-input-placeholder`: #61616a light, #a1a1aa dark; 4.5:1 or more on
|
|
1077
|
+
the resting, hovered, focused and pressed field).
|
|
1078
|
+
- **Checked checkboxes and radios use the primary action fill**, with the
|
|
1079
|
+
primary button's text colour on the tick and dot.
|
|
1080
|
+
- **Dropdown menus, Select lists, the ColorPicker popover and NavigationMenu
|
|
1081
|
+
panels have 16px corners** (were 8px), and other nested corners are
|
|
1082
|
+
concentric: the Card in a Modal, MediaGrid's check mark, Alert's close
|
|
1083
|
+
button, rows in a `.list-group`, the avatar in SidebarFooter's profile
|
|
1084
|
+
button.
|
|
1085
|
+
- **`font-sans` and `font-mono` follow the theme's font tokens**
|
|
1086
|
+
(`--font-family-sans`, `--font-family-mono`), not Tailwind's system stacks.
|
|
1087
|
+
If you used `font-sans` to get the system font, write the stack yourself.
|
|
1088
|
+
- **`color-scheme` comes from the theme**: `light` by default and for
|
|
1089
|
+
`data-theme="light"`, `dark` under `.dark` and `data-theme="dark"` (also when
|
|
1090
|
+
both `class="dark"` and `data-theme="light"` are set). Native controls,
|
|
1091
|
+
scrollbars, date pickers and autofill follow the theme. A
|
|
1092
|
+
`<meta name="color-scheme">` tag is not needed, and a `color-scheme` rule of
|
|
1093
|
+
your own must come after the theme import to win.
|
|
1094
|
+
|
|
1095
|
+
**Markup, roles and accessible names**
|
|
1096
|
+
|
|
1097
|
+
- **A `Select` is found by role `combobox`, not `button`.** Its trigger has
|
|
1098
|
+
`role="combobox"` and reports `aria-invalid`, `aria-required` and
|
|
1099
|
+
`aria-busy`. Its form control is a native `<select name>` (not a hidden
|
|
1100
|
+
input), so code that looked for `input[type=hidden]` must look for the
|
|
1101
|
+
select, and `required` now blocks the form. A form reset returns the Select
|
|
1102
|
+
to the value it was rendered with. Update tests and selectors.
|
|
1103
|
+
- **A loading `Button` or `IconButton` is `aria-disabled="true"` and
|
|
1104
|
+
`aria-busy="true"`, not `disabled`.** It keeps keyboard focus and swallows
|
|
1105
|
+
the press. CSS or tests that matched it with `:disabled`, `[disabled]` or a
|
|
1106
|
+
strict `toBeDisabled()` must use `[aria-busy="true"]`.
|
|
1107
|
+
- **`ThemeToggle` (two modes) has the constant name "Dark mode"** with
|
|
1108
|
+
`aria-pressed`, not "Switch to dark mode" / "Switch to light mode"; update
|
|
1109
|
+
tests and scripts that find the button by those names. It applies the stored
|
|
1110
|
+
choice when it mounts (`storageKey={null}` turns that off) and no longer
|
|
1111
|
+
writes an inline `color-scheme`.
|
|
1112
|
+
- **`NavigationMenu` is a list of links and disclosure buttons**, not a
|
|
1113
|
+
`menubar`: the list has `role="list"` and items carry no menu roles. Screen
|
|
1114
|
+
readers announce it differently; keyboard use is unchanged.
|
|
1115
|
+
- **A `List` row with no `href` in a List with no `onclick` is plain content**,
|
|
1116
|
+
not a focusable button with an arrow.
|
|
1117
|
+
- **Checkbox and Radio inputs are stretched invisibly over their box** instead
|
|
1118
|
+
of being visually hidden, so `getByRole("checkbox").check()` works.
|
|
1119
|
+
- **An unnamed `RadioGroup` submits nothing.** Give it a `name`. (The
|
|
1120
|
+
generated name still groups the radios.)
|
|
1121
|
+
- **A disabled `Dropdown` or `Select` option is `aria-disabled`, not
|
|
1122
|
+
`disabled`**: reachable with the arrow keys, announced as disabled, not
|
|
1123
|
+
selectable.
|
|
1124
|
+
- A Select list and a Dropdown menu are one Tab stop (roving `tabindex`, 0 or
|
|
1125
|
+
-1). `getFocusableElements` skips `tabindex="-1"`.
|
|
1126
|
+
|
|
1127
|
+
**Defaults and behaviour**
|
|
1128
|
+
|
|
1129
|
+
- **A binding that starts `undefined` on a bindable prop is now written back
|
|
1130
|
+
as the default** (`""`, `false`, `null`). `let value = $state()` works with
|
|
1131
|
+
`bind:value` everywhere; no bindable prop has a fallback expression any
|
|
1132
|
+
more.
|
|
1133
|
+
- **Controls are at least 44px on touch screens** (`pointer: coarse`), so
|
|
1134
|
+
layouts there get taller (a checkbox list at an 8px gap is 52px a row).
|
|
1135
|
+
Nothing changes with a mouse.
|
|
1136
|
+
- **Every pressable control shows a `:active` pressed state** that does not
|
|
1137
|
+
depend on hover. Hand-written `:hover` rules apply only under
|
|
1138
|
+
`@media (hover: hover)`, so a tapped button no longer sticks in its hover
|
|
1139
|
+
colour.
|
|
1140
|
+
- **`Page` pads for the safe areas by default** (`safeArea`). An app that sets
|
|
1141
|
+
`viewport-fit=cover` and already pads for the insets itself should pass
|
|
1142
|
+
`safeArea={false}`. Without `viewport-fit=cover` the insets are zero and
|
|
1143
|
+
nothing changes.
|
|
1144
|
+
- **`Select` opens as a bottom sheet on a phone** (a touch screen narrower than
|
|
1145
|
+
640px; `presentation="auto"`). Pass `presentation="popover"` to keep the
|
|
1146
|
+
pop-over. It fills its container like Input (a Select in a flex row takes the
|
|
1147
|
+
free width; give it a width such as `class="w-48"`, which is honoured now),
|
|
1148
|
+
and a label too long for the trigger wraps instead of being cut.
|
|
1149
|
+
`maxMenuHeight` defaults to `60dvh` (was `60vh`).
|
|
1150
|
+
- **`Button` labels wrap.** A label that does not fit goes onto a second line
|
|
1151
|
+
and the button grows (`min-height` instead of a fixed height); in a tight
|
|
1152
|
+
flex row a button may shrink and wrap where it held its width.
|
|
1153
|
+
- **Toasts show your message.** A toast with a `message` and no `title` shows
|
|
1154
|
+
the message, not a default heading; only a toast with `detail` can be
|
|
1155
|
+
expanded; the countdown sentence and "Click to stop" are off unless
|
|
1156
|
+
`showCountdown`. A toast with no `duration` closes after 7 s (14 s when its
|
|
1157
|
+
text is 121 to 240 characters, stays when longer); an error toast and a
|
|
1158
|
+
toast with an action stay until dismissed. On touch, `onpausechange` reports
|
|
1159
|
+
`paused` then `running` for a tap. Toasts also sit above the tab bar, the
|
|
1160
|
+
home indicator and the on-screen keyboard, and move while an overlay with a
|
|
1161
|
+
header and footer is open. Mount the Toaster inside `ZabiStringsProvider`
|
|
1162
|
+
when you use it.
|
|
1163
|
+
- **`Tooltip` opens on a tap and can be hovered.** A touch or pen press toggles
|
|
1164
|
+
it, and it stays open until a second tap, a tap elsewhere, Escape, a scroll
|
|
1165
|
+
or focus leaving (`touchDuration` defaults to `0`). The pointer can move from
|
|
1166
|
+
the trigger onto the open bubble, so an open tooltip takes presses: keep
|
|
1167
|
+
tooltips clear of other controls. A tooltip that would leave the viewport
|
|
1168
|
+
flips or slides to stay 8px inside, and `data-placement` reports the side
|
|
1169
|
+
used.
|
|
1170
|
+
- **`Tabs` with long labels scroll** instead of wrapping onto several lines.
|
|
1171
|
+
- **`AppBar` and `BottomTabBar` stay one row** (titles and labels get an
|
|
1172
|
+
ellipsis; the bars are 57px and 65px at any text size). AppBar can be two
|
|
1173
|
+
rows tall when the title would have under 72px beside the controls.
|
|
1174
|
+
- **Overlays follow the on-screen keyboard** where it covers the page (Modal,
|
|
1175
|
+
BottomSheet, SlideUp, Drawer), and Modal and SlideUp are at most `90dvh`.
|
|
1176
|
+
- **Light-mode `Input`, `Textarea` and `Select` darken their border on hover**
|
|
1177
|
+
and render text at 16px below 640px, so iOS Safari no longer zooms on focus.
|
|
1178
|
+
- **`Table` and `PropsTable` sit on the card surface** with an elevated header
|
|
1179
|
+
band; Skeleton and Header's variant chips use `bg-neutral-subtle`.
|
|
1180
|
+
- **Inside `on-brand` and `on-accent` blocks** labels, links, borders and quiet
|
|
1181
|
+
buttons change to the block's on-colour; a card inside a block gets the
|
|
1182
|
+
page's colours back. Custom properties named `--zabi-theme-*` appear on a
|
|
1183
|
+
block's parent.
|
|
1184
|
+
- **`ImageUpload` no longer forces a 16rem minimum width**, and its `id` names
|
|
1185
|
+
the control (the dropzone button), not the host. Set a width with `class` if
|
|
1186
|
+
you relied on it.
|
|
1187
|
+
- **`SidebarAccountPanel`'s `systemMode` default text is "Follows the
|
|
1188
|
+
system".**
|
|
1189
|
+
- `UnsavedChangesBar` and `ConfirmDialog` leave focus on the pressed button
|
|
1190
|
+
while busy, where they moved it to their container.
|
|
1191
|
+
- `Modal`'s `onclick` as a close signal is deprecated (see Deprecated).
|
|
1192
|
+
|
|
1193
|
+
**Theme files and the generator**
|
|
1194
|
+
|
|
1195
|
+
- **Dark theme files no longer restate the `--zabi-*` ramps.** `theme-dark`,
|
|
1196
|
+
`theme-dark-only` and the dark part of `colors` only remap roles; the raw
|
|
1197
|
+
palettes are declared once, in the light theme. Import the light theme
|
|
1198
|
+
first: `@import "zabi-components/theme-only"; @import
|
|
1199
|
+
"zabi-components/theme-dark-only";`. 8.0.0's `.dark`-only file carried a
|
|
1200
|
+
copy; a dark file imported on its own now has no ramps. An override of
|
|
1201
|
+
`--zabi-brand-*`, `--zabi-accent-*` or `--zabi-base-*` on `:root` applies in
|
|
1202
|
+
both modes. The dark files are about 13 KB larger, because the block is
|
|
1203
|
+
published for the class and attribute and for the system setting. See
|
|
1204
|
+
`docs/theme-imports.md`.
|
|
1205
|
+
- **The generator checks more role pairs than 8.0.0**, so a theme check that
|
|
1206
|
+
passed on 8.0.0 can turn red. `createTheme` and `zabi-theme` resolve every
|
|
1207
|
+
role pair the library's own guard checks (the field edge on fill, page and
|
|
1208
|
+
card; the progress fill on its track; the muted and danger focus rings and
|
|
1209
|
+
the control boundary on all five surface levels; the accent star outline on
|
|
1210
|
+
an inset surface; the focus ring or its offset gap on an accent button; the
|
|
1211
|
+
label on a selected and on a held selected pill tab). A pair below AA (4.5:1
|
|
1212
|
+
text, 3:1 UI parts) is a warning, and fails under `--strict`. The generated
|
|
1213
|
+
values are unchanged; the findings are real for that theme. Fix them with an
|
|
1214
|
+
override (`--set`), for example `--color-focus` and `--color-link-hover` one
|
|
1215
|
+
brand step darker, or use `--pin`.
|
|
1216
|
+
- Theme files work with `[data-theme="dark"]`, and with `[data-theme="auto"]`
|
|
1217
|
+
under `prefers-color-scheme: dark`. A page with no class and no attribute is
|
|
1218
|
+
light, as before.
|
|
1219
|
+
|
|
1220
|
+
**Package**
|
|
1221
|
+
|
|
1222
|
+
- `package.json`: new `./create-theme` export and `zabi-theme` bin; `culori`
|
|
1223
|
+
moved to `dependencies` (the generator uses it at run time);
|
|
1224
|
+
`sideEffects: ["**/*.css"]`; `CHANGELOG.md` shipped in `files`; the `prepare`
|
|
1225
|
+
script renamed `fix:lucide-types` (dev-only, so installing prints no
|
|
1226
|
+
install-scripts notice; run it if your editor needs it). Every component
|
|
1227
|
+
imports icons from `@lucide/svelte/icons/<name>`; import yours that way too.
|
|
1228
|
+
No export, prop, token or file from 8.0.0 was removed or renamed.
|
|
1229
|
+
|
|
1230
|
+
### Added
|
|
1231
|
+
|
|
1232
|
+
#### Theming and the `zabi-theme` generator
|
|
1233
|
+
|
|
1234
|
+
- **`npx zabi-theme --brand "#0026EA"`** writes a CSS file with the full
|
|
1235
|
+
`--zabi-brand-*` ramp; with `--accent` and `--neutral` also the
|
|
1236
|
+
`--zabi-accent-*` ramp and all 21 `--zabi-base-*` steps; `--out` names the
|
|
1237
|
+
file. Import it after `theme-only` and `theme-dark-only` and it covers light
|
|
1238
|
+
and dark. The same is available in code as `createTheme({ brand, accent,
|
|
1239
|
+
neutral })` from `zabi-components/create-theme`, which returns `{ css, tokens,
|
|
1240
|
+
warnings, closest }`. The ramps sit on the library's lightness curve: your
|
|
1241
|
+
colour gives the hue and chroma and is not pinned to a step, so the exact hex
|
|
1242
|
+
may not appear, and the file header names the closest step. The generator
|
|
1243
|
+
picks the text colour for the brand and accent fills.
|
|
1244
|
+
- **The generator reports contrast** on stderr, in the file header and in
|
|
1245
|
+
`warnings`; `--strict` exits 1 on a failed pair. `--set <token>=<value>` (or
|
|
1246
|
+
`overrides`) moves a role and is checked too. `overrides` takes a flat map
|
|
1247
|
+
(both modes) or `{ light, dark, both }`; the CLI has `--set-light` and
|
|
1248
|
+
`--set-dark`. A light-only override is restated with the library's own dark
|
|
1249
|
+
value in the dark rules, and the check reads each mode's own value.
|
|
1250
|
+
- **`zabi-theme --pin`** (`createTheme({ pin: true })`): the primary action is
|
|
1251
|
+
the exact brand colour in light, with derived hover and pressed states and a
|
|
1252
|
+
label checked at 4.5:1. The focus ring and links take the colour where every
|
|
1253
|
+
guarded pair still passes. Dark keeps the mirrored ramp step unless the colour
|
|
1254
|
+
passes there; the header says which. A failing pair is a warning and the
|
|
1255
|
+
colour is never moved. `--pin-accent` / `pin: { accent: true }` does the same
|
|
1256
|
+
for the solid accent fill.
|
|
1257
|
+
- **`neutralChroma`** (CLI `--neutral-chroma`, 0 to 0.1, needs `neutral`): the
|
|
1258
|
+
OKLCH chroma at the neutral ramp's peak, for a clearly tinted neutral. With it
|
|
1259
|
+
the translucent ink roles (`--color-action-secondary` and its hover and
|
|
1260
|
+
pressed steps, `--color-surface-hover`, `-active`, `--color-border-overlay`
|
|
1261
|
+
in light, `--shadow-color`) follow the neutral ramp, and the file restates the
|
|
1262
|
+
dark values under the dark selectors.
|
|
1263
|
+
- **A theming guide.** `THEMING.md` is the documented token API: quick start
|
|
1264
|
+
with `zabi-theme`, import order, tables of the tokens an app may set (brand,
|
|
1265
|
+
accent and neutral ramps, the "on" colours, fonts and weights, radius,
|
|
1266
|
+
shadow, z-index) and the roles to read, light, dark and auto, what the
|
|
1267
|
+
generator does and does not do, and what does not follow an override.
|
|
1268
|
+
Renaming or removing a documented token is a breaking change; the token-name
|
|
1269
|
+
snapshot in `npm run test:themes` enforces it. The docs site has a `/theming`
|
|
1270
|
+
page with a brand switcher (`?brand=amber` opens any page in Amber, generated
|
|
1271
|
+
from `#C17B00` and a warm neutral).
|
|
1272
|
+
- **One brand override restyles light and dark.** The raw `--zabi-*` ramps are
|
|
1273
|
+
declared once and the dark block only remaps roles; dark surfaces are mixed
|
|
1274
|
+
from the neutral ramp, so warm or tinted greys carry through.
|
|
1275
|
+
- **An accent colour.** `--zabi-accent-50` … `950`, `--color-accent-50` …
|
|
1276
|
+
`950`, the roles `--color-accent`, `-hover`, `-active`, `-subtle`, `-border`,
|
|
1277
|
+
`-text`, `--color-on-accent`, and the classes `bg-accent`, `text-accent`,
|
|
1278
|
+
`border-accent` (with working `hover:` and `active:` forms, also
|
|
1279
|
+
`active:bg-info-active` and `hover:bg-nav-menu-active-hover`). The
|
|
1280
|
+
`energetic` tokens are unchanged. `Button` and `IconButton` take
|
|
1281
|
+
`variant="accent"` (solid, own hover and pressed fills, `--color-on-accent`
|
|
1282
|
+
label); `Badge variant="accent"` (subtle and solid); `Rating tone="accent"`.
|
|
1283
|
+
- **The text on a fill can be set.** `--zabi-on-brand` (light, white) and
|
|
1284
|
+
`--zabi-on-brand-dark` (dark, `brand-950`) feed `--color-on-brand`, which
|
|
1285
|
+
`--color-action-primary-text` follows; `--zabi-on-accent` and
|
|
1286
|
+
`--zabi-on-accent-dark` do the same for the accent. `Heading` and `Text` take
|
|
1287
|
+
`tone="inherit" | "on-brand" | "on-accent"`; the classes `on-brand` and
|
|
1288
|
+
`on-accent` set a block's text colour and make the focus ring visible on it;
|
|
1289
|
+
`on-surface` marks a surface of your own inside a block.
|
|
1290
|
+
THEMING.md ("Text and controls on a brand or accent block") says which button
|
|
1291
|
+
variants stay legible there. Directly on a block an empty or ticked checkbox,
|
|
1292
|
+
radio or toggle, a filled Rating star and status message text are not legible
|
|
1293
|
+
(put them on a card); a card inside a block needs `:has()`.
|
|
1294
|
+
- **Font tokens.** `--font-family-heading` (follows `--font-family-sans` until
|
|
1295
|
+
set; applied to `h1`–`h6` and Heading), `--font-family-mono` (CodeBlock reads
|
|
1296
|
+
it), `--font-weight-regular`, `-normal`, `-medium`, `-semibold`, `-bold`,
|
|
1297
|
+
`--font-sans`, `--font-mono`, `--font-heading`, and a `font-heading` utility.
|
|
1298
|
+
- **Dark mode by attribute and by the system setting.** The dark files apply to
|
|
1299
|
+
`.dark`, `[data-theme="dark"]`, and `[data-theme="auto"]` when
|
|
1300
|
+
`prefers-color-scheme` is dark; `[data-theme="light"]` stays light. Following
|
|
1301
|
+
the system is opt-in. Put the class or attribute on `<html>`.
|
|
1302
|
+
- **ThemeToggle `modes="three"`** steps through system, light and dark, writes
|
|
1303
|
+
`data-theme` on `<html>`, shows a monitor, sun or moon and names itself
|
|
1304
|
+
"Theme: system. Switch to light"; `labels` (including `labels.darkMode`)
|
|
1305
|
+
replaces the words. `mode` (bindable), `onmodechange` and `storageKey` read or
|
|
1306
|
+
drive the mode, or store it under your own key. It reads `dark`, `light` and
|
|
1307
|
+
`auto`, follows changes made elsewhere, and shows the right icon before it
|
|
1308
|
+
mounts.
|
|
1309
|
+
- **Theme helpers** from the package root: `getThemeMode`, `setThemeMode`,
|
|
1310
|
+
`isThemeDark`, `getStoredThemeMode`, `storeThemeMode`, `themeInitScript` and
|
|
1311
|
+
the `ThemeMode` type. `themeInitScript()` returns a script for `<head>` that
|
|
1312
|
+
applies the stored mode before first paint. TopNavbar takes `themeModes`,
|
|
1313
|
+
`themeStorageKey` and `themeLabels`; SidebarAccountPanel takes
|
|
1314
|
+
`themeModes="three"` with `onThemeModeChange`.
|
|
1315
|
+
- New tokens: `--color-surface-inset` (`bg-surface-inset`; a recessed well on a
|
|
1316
|
+
card, `base-100` light and `base-150` dark, AA text; dark `--color-input`
|
|
1317
|
+
points at it and keeps its value; do not use `--color-input` for a well, it is
|
|
1318
|
+
white in light), `--color-input-border-hover`, `--color-control-border`
|
|
1319
|
+
(`border-control-border`, `text-control-border`; 3:1 on every surface level;
|
|
1320
|
+
`--color-border-strong` is unchanged and decorative), `--color-focus-ring-muted`
|
|
1321
|
+
(`.focus-ring--muted`), `--color-focus-ring-danger` (the error colour; the
|
|
1322
|
+
on-colour inside a block), `--color-input-active` (`active:bg-input-active`;
|
|
1323
|
+
Select's trigger uses it), `--color-action-primary-subtle-active` and
|
|
1324
|
+
`--color-action-danger-subtle-active` (`active:bg-…`), `--zabi-list-row-radius`
|
|
1325
|
+
(`.list-group` sets it for rows), `--color-progress-fill`,
|
|
1326
|
+
`--color-progress-track`, `--zabi-avatar-ring`, and the documented star
|
|
1327
|
+
colours `--zabi-rating-on`, `--zabi-rating-off`, `--zabi-rating-on-hover`,
|
|
1328
|
+
`--zabi-rating-on-active`, `--zabi-rating-on-edge` (they apply when set on an
|
|
1329
|
+
ancestor or by a class). The contrast guard holds the muted and danger focus
|
|
1330
|
+
rings and the control boundary to 3:1 on all five surface levels, and fails
|
|
1331
|
+
when a focus-ring rule reads a colour that is not in its pair list.
|
|
1332
|
+
|
|
1333
|
+
#### New components
|
|
1334
|
+
|
|
1335
|
+
- **SortableList** reorders items and leaves their content to you. Each row
|
|
1336
|
+
has a drag handle (mouse and touch), arrow keys, Home and End on the handle,
|
|
1337
|
+
optional move up and down buttons and a polite announcement of the new
|
|
1338
|
+
position. `bind:items`, `onreorder` (item, old and new index),
|
|
1339
|
+
`controls="manual"`, `strings`. Escape cancels a drag; a key that cannot move
|
|
1340
|
+
further says so ("Hero section, already first"); a parent replacing `items`
|
|
1341
|
+
mid-drag cancels it. Clicks on the handle or a move button do not bubble: put
|
|
1342
|
+
them beside a header toggle, never inside another button. Handle and buttons
|
|
1343
|
+
sit 8px apart on touch.
|
|
1344
|
+
- **Collapsible**: a trigger wired to the panel it shows and hides, owning ids,
|
|
1345
|
+
`aria-expanded`, `aria-controls` and the panel's name. `title` gives a
|
|
1346
|
+
full-width header button with a chevron (`headingLevel` wraps it in a real
|
|
1347
|
+
heading); a `trigger` snippet hands the wiring to your own `<button>`.
|
|
1348
|
+
`bind:open`, `onopenchange`, `disabled`. Closed content stays in the DOM under
|
|
1349
|
+
`hidden` (`unmountOnClose` removes it). If the panel closes while focus is
|
|
1350
|
+
inside it, focus moves to the trigger. **CollapsibleGroup** makes an
|
|
1351
|
+
accordion: opening one closes the others, or `multiple`; Arrow Up and Down,
|
|
1352
|
+
Home and End move between headers. In a single-open group only the first
|
|
1353
|
+
panel marked open renders open (also on the server, without
|
|
1354
|
+
`onopenchange`); a disabled panel keeps its state.
|
|
1355
|
+
- **ConfirmDialog** (on Modal): `title`, `message`, `variant` (`danger`,
|
|
1356
|
+
`warning`, `info`, each with an icon; `danger` uses the danger button),
|
|
1357
|
+
`confirmLabel`, `cancelLabel`. If `onconfirm` returns a promise the dialog
|
|
1358
|
+
shows its loading state and cannot be dismissed until it settles: it closes on
|
|
1359
|
+
success, stays open on failure and passes the error to `onerror`; returning
|
|
1360
|
+
`false` keeps it open. `loading` does the same for your own request tracking;
|
|
1361
|
+
`oncancel` reports how the user backed out; `loadingLabel` (default
|
|
1362
|
+
"Working…") is announced once. Focus starts on Cancel, Enter only activates
|
|
1363
|
+
the focused button, and it renders in `document.body` unless `portal={false}`.
|
|
1364
|
+
It uses `role="alertdialog"`, draws no outline around the whole dialog while
|
|
1365
|
+
loading and sets no `aria-busy` on the panel.
|
|
1366
|
+
- **Drawer**: a modal panel from the side. `side` is `left`, `right`, `start` or
|
|
1367
|
+
`end`; `size` is `sm`, `md` or `lg`, never wider than the screen; `title`,
|
|
1368
|
+
`description`, a `footer` snippet; `dismissible`, `onclose({ reason })`,
|
|
1369
|
+
`closeLabel`, `initialFocus`, `portal` (default true). Focus is trapped and
|
|
1370
|
+
returns to the opener, the page does not scroll, the slide is skipped under
|
|
1371
|
+
`prefers-reduced-motion`, it shares the scroll lock with Modal and SlideUp and
|
|
1372
|
+
stacks with them. Its `onkeydown` runs after Escape handling; content with
|
|
1373
|
+
nothing to focus is a Tab stop named by the title; the panel pads for the
|
|
1374
|
+
bottom safe area; the close button takes taps in a 44px area.
|
|
1375
|
+
- **MediaGrid**: image and video thumbnails to pick from (the grid only).
|
|
1376
|
+
`getKey`, `getLabel`, `getUrl`; `bind:selected` or, with `multiple`,
|
|
1377
|
+
`bind:selectedKeys`. A single selection moves and is never cleared by a repeat
|
|
1378
|
+
press. `ondelete` shows a delete button named after each item (focus moves to
|
|
1379
|
+
the item that took its place); `getType`, `getPoster`, `loading`, `empty`
|
|
1380
|
+
snippet, `emptyHeadingLevel` (default 3), `minTileSize` (96px), `strings`. One
|
|
1381
|
+
Tab stop; arrow keys move by the columns on screen, Home and End within the
|
|
1382
|
+
row, Delete asks to delete; "Use the arrow keys to move between items." is read
|
|
1383
|
+
when focus enters the grid and shown below it while it has keyboard focus. The delete button sits on the end corner, mirrors
|
|
1384
|
+
in RTL, takes taps in a 44px area and shows a pressed state on touch.
|
|
1385
|
+
- **Spinner**: the loading ring on its own. Sizes `xs` to `lg` (12, 14, 16,
|
|
1386
|
+
20px); with a `label` it is a `status`, without one it is decorative; fades
|
|
1387
|
+
instead of spinning under `prefers-reduced-motion`.
|
|
1388
|
+
- **Slider**: a native range input in the library's colours. `bind:value`,
|
|
1389
|
+
`min`, `max`, `step`, label, helper or error text, three sizes, `showValue`,
|
|
1390
|
+
`formatValue` (also `aria-valuetext`).
|
|
1391
|
+
- **UnsavedChangesBar**: Save and Discard while a form is `dirty`; sticky
|
|
1392
|
+
(`position` `bottom` or `top`); `onsave` may return a promise (saving state;
|
|
1393
|
+
a rejection keeps the bar and calls `onerror`); appearance announced politely;
|
|
1394
|
+
never takes focus; focus returns to the edited field when it goes; `actions`
|
|
1395
|
+
snippet. Warning before the page is left is your app's job.
|
|
1396
|
+
- **AppShell**: the phone layout. `header` snippet (AppBar), `<main>` content,
|
|
1397
|
+
`footer` snippet (BottomTabBar), a `dvh` column inside the safe areas. Sets
|
|
1398
|
+
`--app-shell-top-inset` and `--app-shell-bottom-inset` (fallbacks 57px and
|
|
1399
|
+
65px) on itself and on `<html>` while mounted, for overlays in
|
|
1400
|
+
`document.body`. `contentElement="div"` inside a page that already has a
|
|
1401
|
+
`<main>`.
|
|
1402
|
+
- **AppBar**: a phone top bar with `title` (`headingLevel`, default 1;
|
|
1403
|
+
`titleLines={2}` allows two lines), back control (`backHref` or `onback`,
|
|
1404
|
+
`backLabel`), `leading` and `actions` snippets (up to two actions),
|
|
1405
|
+
`collapseOnScroll` (stays while focus is inside; no animation under reduced
|
|
1406
|
+
motion). The title always has at least 72px; a `leading` wider than the row is
|
|
1407
|
+
held to the row. Parts carry `data-appbar-part` (`back`, `title`, `actions`);
|
|
1408
|
+
the bar measures itself in the browser, so before hydration the title wraps to
|
|
1409
|
+
the second row. At 180px wide no title shows beside a back control and two
|
|
1410
|
+
actions.
|
|
1411
|
+
- **BottomTabBar**: three to five links with an icon above a short label.
|
|
1412
|
+
`items` take `href`, `label`, `icon`, `badge` (read with the label: "Inbox, 3
|
|
1413
|
+
new"; `badgeLabel`, `badgeMax`). `active` is an href or path (`aria-current=
|
|
1414
|
+
"page"`); left out, the bar follows the browser address, so pass
|
|
1415
|
+
`active={page.url.pathname}` on the server. Fixed on its own and in the flow
|
|
1416
|
+
in an AppShell (`position`), clear of the home indicator. Tabs are at least
|
|
1417
|
+
44px with 8px gaps; under 52px wide the labels hide (icon only, label stays
|
|
1418
|
+
the accessible name); tabs go under 44px only when five do not fit. The
|
|
1419
|
+
active tab has a 2px outline in the action colour. With five tabs labels stay
|
|
1420
|
+
11 to 12px at 200% text.
|
|
1421
|
+
- **Rating**: one to `max` (default 5) stars, can be empty. `bind:value` is a
|
|
1422
|
+
number or `null`; a radio group named by `label`, each star reads "4 of 5
|
|
1423
|
+
stars"; arrow keys, Home and End; `name` submits it. A repeat press on the
|
|
1424
|
+
selected star never clears it; `clearable` adds a clear button (Delete and
|
|
1425
|
+
Backspace clear too). `readonly` shows a score as one image ("Quiz, 3.5 of 5
|
|
1426
|
+
stars"), with fractions, `showValue`, `formatValue`; it submits nothing. 44px
|
|
1427
|
+
targets at every `size`; `strings`; the empty star uses
|
|
1428
|
+
`--color-control-border`.
|
|
1429
|
+
- **SegmentedControl**: two to four choices in one row. `options` (`value`,
|
|
1430
|
+
`label`, `icon`, `disabled`), `bind:value`, `onchange`. A radio group, not
|
|
1431
|
+
tabs: one Tab stop, arrows move and select, `name` submits, a repeat press
|
|
1432
|
+
does nothing. `fullWidth` (default true), labels wrap, 44px tall on touch.
|
|
1433
|
+
- **BottomSheet**: a modal panel sliding up from the bottom. Snap points
|
|
1434
|
+
(`snapPoints`, `"half"` and `"full"` by default; `bind:snap`); the grip drags
|
|
1435
|
+
between them or down to close and is a button ("Expand" / "Collapse"). Content
|
|
1436
|
+
scroll wins over dragging unless at its top. `title`, `description`, `footer`,
|
|
1437
|
+
`isOpen`, `dismissible`, `portal`, `initialFocus`, `closeLabel`,
|
|
1438
|
+
`onclose({ reason })` with the extra reason `"swipe"`. Safe areas, no animation
|
|
1439
|
+
under reduced motion, a centred 40rem sheet from `md`. Half and full heights
|
|
1440
|
+
are shares of what the on-screen keyboard leaves. The grip's focus ring is
|
|
1441
|
+
drawn inside its 44px box; a click reaches the grip button, and flicks are
|
|
1442
|
+
timed from the events' own times.
|
|
1443
|
+
- **StickyActionBar**: keeps a form's main action in view at the bottom of the
|
|
1444
|
+
screen or scrolling box, rises above the on-screen keyboard, reserves
|
|
1445
|
+
`scroll-padding-bottom`, never takes focus. One per scrolling box; a short
|
|
1446
|
+
form needs `class="flex min-h-full flex-col"`.
|
|
1447
|
+
- **FloatingActionButton**: a round 56px primary button. `label` (required, the
|
|
1448
|
+
accessible name), `extended`, `icon`, `href` (a link; a button otherwise),
|
|
1449
|
+
`position` (`bottom-end` default, `bottom-start`, `bottom-center`). Above the
|
|
1450
|
+
tab bar in an AppShell, above the safe area elsewhere; `--fab-bottom-offset`
|
|
1451
|
+
lifts it; it reserves `scroll-padding-bottom` on what scrolls under it.
|
|
1452
|
+
- **DateField** and **TimeField**: the browser's date and time inputs in the
|
|
1453
|
+
library's Input. `bind:value` is `YYYY-MM-DD` or 24-hour `HH:mm`, `""` when
|
|
1454
|
+
empty; `label`, `hint`, `error`, `min`, `max`, `step`, `required`, `disabled`,
|
|
1455
|
+
`readonly`, `size`, `name`; work in FormField; ids start with `input-`.
|
|
1456
|
+
**`formatDate` and `formatTime`** (package root) format those strings for a
|
|
1457
|
+
locale (`formatDate("2026-10-06", "sv")` is "6 okt. 2026"), never shift by the
|
|
1458
|
+
time zone, and return `""` for empty or invalid.
|
|
1459
|
+
- **Calendar**: one month as an ARIA grid with event dots. `bind:month`
|
|
1460
|
+
(`YYYY-MM`), `bind:selected` (`YYYY-MM-DD` or `null`), `events` (`date`,
|
|
1461
|
+
`label`, `tone`; up to three dots a day), `weekStartsOn` (default Monday),
|
|
1462
|
+
`locale`, `min`, `max`, `isDateDisabled`, `onselect`, `onmonthchange`,
|
|
1463
|
+
`strings`. One Tab stop: arrows by day and week, Home and End within the
|
|
1464
|
+
week, Page Up and Down by month (Shift: year). Days are named in full with
|
|
1465
|
+
state and events and are 44px tall; unavailable days have a line through the
|
|
1466
|
+
number. `locale` translates dates only, so pass `strings` for "today",
|
|
1467
|
+
"selected", "unavailable". Pressing the selected day does nothing.
|
|
1468
|
+
- **DropdownItem**: the menu item as a component for a Dropdown's custom
|
|
1469
|
+
`children`; takes its role from the Dropdown and joins the arrow-key order. A
|
|
1470
|
+
DropdownItem with custom children and a description is named by its content
|
|
1471
|
+
alone.
|
|
1472
|
+
- **Stepper**: progress through a multi-step form. `steps` (labels or `{ label,
|
|
1473
|
+
description }`), `bind:current` (zero-based), `layout` (auto goes compact below
|
|
1474
|
+
30rem of its own width: it suits about five short labels; compact segments are
|
|
1475
|
+
narrower than 44px from seven steps with `interactive` on 320px), `size`
|
|
1476
|
+
(`sm`, `md`, `lg`), `strings`. Completed steps show a check, the current one
|
|
1477
|
+
its number, upcoming an outline; a navigation landmark with
|
|
1478
|
+
`aria-current="step"`; a step change is announced once. `interactive` makes
|
|
1479
|
+
completed steps buttons. It does not move focus: focus the new heading
|
|
1480
|
+
yourself. In Safari 16 and older it needs a width from its parent in a flex
|
|
1481
|
+
row.
|
|
1482
|
+
- **PhotoGrid**: square thumbnails that open a viewer. `photos` (`src`,
|
|
1483
|
+
`thumbSrc`, `alt`, `width`, `height`, `caption`, `id`), `columns` (3, then 4
|
|
1484
|
+
and 5), `max` ("+N" tile), `onopen(index)`, `onadd` ("Add photo" tile),
|
|
1485
|
+
`selectable` (`"single"` or `"multiple"`, `bind:selected`,
|
|
1486
|
+
`bind:selectedKeys`; a single selection is never cleared by a repeat press and
|
|
1487
|
+
each tile gets an Open button). One Tab stop, lazy loading, placeholder and
|
|
1488
|
+
failure fallback. **PhotoViewer**: a full-screen dialog for them, `bind:index`,
|
|
1489
|
+
`bind:isOpen`, caption, counter, up to three `actions` (more in a menu).
|
|
1490
|
+
Swipe or arrows change photo; pinch, double tap, Ctrl+wheel or `+`/`-` zoom;
|
|
1491
|
+
drag pans; swipe down, Escape or the close button close it
|
|
1492
|
+
(`onclose({ reason })`). Focus returns to the tile; the thumbnail shows
|
|
1493
|
+
blurred until the full image loads; neighbours are preloaded; dark in both
|
|
1494
|
+
themes; safe areas. Double tap is timed from the touch events. Previous and
|
|
1495
|
+
Next at the ends keep their plate and dim only the arrow.
|
|
1496
|
+
- **Avatar** and **AvatarGroup**: `Avatar` is a round picture falling back to
|
|
1497
|
+
initials (`name`, `src`, `size` `sm` 24, `md` 32, `lg` 48px, `alt`, `alt=""`
|
|
1498
|
+
for decorative, `locale`); initials come from the first and last word, are in
|
|
1499
|
+
the server markup and remain if the picture fails. They are `brand-200` on
|
|
1500
|
+
`brand-700` in the default light theme (the subtle primary pair, 4.5:1).
|
|
1501
|
+
`AvatarGroup` shows `people` overlapped as a list, up to `max` (default 4) and
|
|
1502
|
+
"+3" (`strings.more`), `locale`. Not interactive.
|
|
1503
|
+
- **SwipeableListItem**: a row with one or two `actions` revealed by a swipe
|
|
1504
|
+
towards the inline start (touch and stylus; mirrored in RTL); a swipe only
|
|
1505
|
+
reveals, an action runs on press. The same actions are behind a "more"
|
|
1506
|
+
button (`showMoreButton`, on by default; with `false` offer another route).
|
|
1507
|
+
Bindable `open`, `onopenchange`, `strings.actions`; one row open per list.
|
|
1508
|
+
- **PullToRefresh**: wraps a list; pulling down at the top calls `onrefresh`
|
|
1509
|
+
(touch only). Bindable `refreshing`, `disabled`, `threshold` (64px),
|
|
1510
|
+
`strings`. A Refresh button shown on keyboard focus does the same; a status
|
|
1511
|
+
region says "Refreshing" then "Updated". A rejected `onrefresh` ends the busy
|
|
1512
|
+
state silently: report the failure in the app. In controlled mode it says
|
|
1513
|
+
"Updated" whenever `refreshing` goes back to false.
|
|
1514
|
+
- **`focusToasts()`** moves focus to the newest toast; focus returns when it is
|
|
1515
|
+
dismissed, also from inside an open overlay.
|
|
1516
|
+
|
|
1517
|
+
#### Existing components
|
|
1518
|
+
|
|
1519
|
+
- **ImageUpload**: `onbrowse` replaces the native chooser and
|
|
1520
|
+
`event.preventDefault()` in `onclick` keeps it closed. A video value (file
|
|
1521
|
+
type, extension, or `previewType`) renders a `<video>` with controls, never
|
|
1522
|
+
autoplaying; a `preview` snippet replaces the preview. `label` and `id` (the
|
|
1523
|
+
dropzone is a native `<button>`); Change and Remove are named from the label
|
|
1524
|
+
("Change logo") or `changeLabel` and `removeLabel`; the preview takes `alt`.
|
|
1525
|
+
A dropped file is checked against `accept` and rejected through
|
|
1526
|
+
`onfilereject`. Copy props: `browseText`, `changeText`, `removeText`,
|
|
1527
|
+
`errorTitle`, `errorRecovery` (`false` leaves the line out), `selectedText`,
|
|
1528
|
+
`removedText` ("Image selected" / "Image removed" are announced);
|
|
1529
|
+
`actionsPlacement` (`"overlay"` or `"strip"`, strip by default for video).
|
|
1530
|
+
- **IconButton**: `xs` size (24px, the minimum target; use `sm` or larger where
|
|
1531
|
+
touch is primary); `pressed` (bindable, `aria-pressed` and a pressed style in
|
|
1532
|
+
every variant; `event.preventDefault()` in `onclick` keeps the state; left
|
|
1533
|
+
undefined it renders no `aria-pressed`); `tone="danger"` on `ghost` or
|
|
1534
|
+
`outline` for a quiet destructive style. `href` on Button and IconButton
|
|
1535
|
+
renders a real link (`target`, `rel`, `download`); a disabled or loading link
|
|
1536
|
+
has no `href` and is `aria-disabled`.
|
|
1537
|
+
- **Modal**: `portal` (renders in `document.body`; a theme class set below
|
|
1538
|
+
`<body>` does not reach it); `onclose({ reason: "escape" | "backdrop" |
|
|
1539
|
+
"close-button" })`; `dismissible={false}` (the close button stays focusable,
|
|
1540
|
+
`aria-disabled`; setting `isOpen` still closes); `role="alertdialog"`;
|
|
1541
|
+
`closeLabel` (default "Close"); `fullScreen` (or `"mobile"` below `md`);
|
|
1542
|
+
`initialFocus` (also on SlideUp); typed `aria-describedby`, `aria-busy`,
|
|
1543
|
+
`data-*`, `id`. SlideUp: `closeLabel`, `swipeToClose` (grip, downward swipe,
|
|
1544
|
+
calls `onclick`), a `footer` snippet kept above the keyboard.
|
|
1545
|
+
- **Toast and Toaster**: `pushToast` takes `action: { label, onclick,
|
|
1546
|
+
dismissOnClick? }` (a button in the toast, announced with the message, closes
|
|
1547
|
+
after the handler unless `dismissOnClick` is `false`; the countdown pauses
|
|
1548
|
+
while pointer or focus is on it; give it a long `duration` or `0`; the
|
|
1549
|
+
`ToastAction` type is exported); `duration` takes `"short"` (3 s), `"medium"`
|
|
1550
|
+
(7 s), `"long"` (14 s), `"persistent"` or milliseconds, with `defaultDuration`
|
|
1551
|
+
on Toaster (`TOAST_DURATIONS`, `ToastDuration` exported); `strings` for every
|
|
1552
|
+
built-in word (`ToasterStrings`, `DEFAULT_TOASTER_STRINGS`), `aria-label`,
|
|
1553
|
+
`showCountdown`, `onpausechange({ id, paused })`, `data-paused`; `closeLabel`
|
|
1554
|
+
on Toast and Alert; `--toaster-bottom-offset` (72px clears a
|
|
1555
|
+
FloatingActionButton); other attributes pass to the region. A held finger
|
|
1556
|
+
pauses a toast, lifting resumes with at least 3 s left. Toasts do not fly
|
|
1557
|
+
under reduced motion; one with an action stays until dismissed.
|
|
1558
|
+
- **Dropdown**: options take `icon`, `tone: "danger"` and `description` (the
|
|
1559
|
+
`DropdownOption` type is exported). `presentation` (`"auto"`, `"popover"`,
|
|
1560
|
+
`"sheet"`; default `"popover"`): in a sheet the same menu opens in a
|
|
1561
|
+
BottomSheet titled by the control's label; `<Dropdown presentation="sheet">`
|
|
1562
|
+
is the library's action sheet (`sheetTitle`, `sheetSnap`, `sheetCloseLabel`,
|
|
1563
|
+
`sheetExpandLabel`, `sheetCollapseLabel`, `fullWidth`, optional
|
|
1564
|
+
`ariaLabelledby`). Menu boxes report `data-resolved-placement`.
|
|
1565
|
+
- **Select**: renders a real `<select>` on the server, the working control
|
|
1566
|
+
before hydration and with scripts off; on mount the custom list takes over,
|
|
1567
|
+
and a choice made before is kept and reported once; `presentation="native"`
|
|
1568
|
+
renders only the native select. `presentation` (`"auto"`, `"popover"`,
|
|
1569
|
+
`"sheet"`, `"native"`), `strings` (including `listLabel`;
|
|
1570
|
+
`DEFAULT_SELECT_STRINGS`, `SelectStrings`, `SelectPresentation` exported,
|
|
1571
|
+
also from atoms; the six text props still win). The chosen option has a check
|
|
1572
|
+
mark at the inline end and a fill, and every option carries `aria-selected`.
|
|
1573
|
+
A disabled Select submits its value once hydrated and not before; the inline
|
|
1574
|
+
message of a `required` Select is the browser's, in the browser's language; a
|
|
1575
|
+
label longer than one line is cut in the native select and wraps in the custom
|
|
1576
|
+
one, so the field can grow by a line.
|
|
1577
|
+
- **Input, Textarea, Select**: `hint` and `error` (a hint is tied with
|
|
1578
|
+
`aria-describedby`, an error sets the error state and is announced;
|
|
1579
|
+
`variant` + `message` still work and your own `aria-describedby` is merged).
|
|
1580
|
+
Input takes `leading` and `trailing` snippets and, for `type="password"`,
|
|
1581
|
+
`revealable` (`revealLabel`, default "Show password", `aria-pressed`). Input,
|
|
1582
|
+
Textarea, Select, Checkbox, Radio, Toggle and ThemeToggle accept their
|
|
1583
|
+
element's native attributes (`autocomplete`, `inputmode`, `maxlength`,
|
|
1584
|
+
`enterkeyhint`, `data-*`); Table and Text pass other attributes on. Toggle
|
|
1585
|
+
takes `aria-label` and `aria-labelledby` ("Toggle" is only the last fallback).
|
|
1586
|
+
`Checkbox` is exported from the atoms entry.
|
|
1587
|
+
- **Table**: `stacked` (or `"sm"`, `"md"`, `"lg"`) lays rows out as label and
|
|
1588
|
+
value pairs and drops the 20rem minimum; put `data-label` on each cell and
|
|
1589
|
+
keep the `<thead>`; `captionHidden` keeps the caption as the accessible name
|
|
1590
|
+
but hides it. A stacked cell keeps several children together on the value
|
|
1591
|
+
side; a cell without `data-label` puts its content there too; an empty cell
|
|
1592
|
+
leaves no blank line.
|
|
1593
|
+
- **EmptyState**: `headingLevel` (1–6, default 2); `size="compact"` (tighter,
|
|
1594
|
+
smaller title, a plain `<div>` not a named region; the default is still a
|
|
1595
|
+
`<section>` named by its title).
|
|
1596
|
+
- **TopNavbar**: nav items take `external` (default true for absolute URLs);
|
|
1597
|
+
`collapseAt` (`"sm"`, `"md"` default, `"lg"`, `"xl"`; `TopNavbarCollapseAt`
|
|
1598
|
+
exported).
|
|
1599
|
+
- **Tabs**: `fullWidth` (for two or three tabs; scrolls when a share is too
|
|
1600
|
+
small for the longest word). The rule under Tabs is `border-border`; a
|
|
1601
|
+
selected pill uses `action-primary-subtle`.
|
|
1602
|
+
- **SidebarShell**: `mobile="drawer"` hides the sidebar below `lg` and opens it
|
|
1603
|
+
in a Drawer from the start side: `bind:isOpen`, `trigger` snippet
|
|
1604
|
+
(`aria-expanded` wiring), `drawerTitle`, `closeLabel`, `onclose({ reason })`;
|
|
1605
|
+
it closes when a link is followed and when the screen widens past 1024px;
|
|
1606
|
+
`label` names the scrolling region (default "Navigation links").
|
|
1607
|
+
SidebarNavigation declares and passes these on. Default `mobile="none"`
|
|
1608
|
+
changes nothing.
|
|
1609
|
+
- **Page**: `safeArea` (default true) pads left, right and bottom by
|
|
1610
|
+
`env(safe-area-inset-*)`; nothing inside an AppShell or where the insets are
|
|
1611
|
+
zero; your own `px-*` class replaces that side. Needs `viewport-fit=cover`.
|
|
1612
|
+
- **Tooltip**: `touchDuration` (default `0`); one open at a time; a tooltip on a
|
|
1613
|
+
disabled button is tied to it with `aria-describedby`. Do not put essential
|
|
1614
|
+
information in a tooltip.
|
|
1615
|
+
- **ColorPicker**: the colour map is two native sliders (saturation, lightness)
|
|
1616
|
+
in one Tab stop, working by keyboard (Shift moves ten steps), touch and pen;
|
|
1617
|
+
new strings `area`, `saturation`, `lightness`, `invalidHex`; the hue slider
|
|
1618
|
+
shows its focus ring and is 44px on a coarse pointer.
|
|
1619
|
+
- **CodeBlock**: `copyLabel`, `copiedLabel`; "copied" is announced through a
|
|
1620
|
+
status region. **FormField**: `requiredLabel`.
|
|
1621
|
+
#### Mobile and touch
|
|
1622
|
+
|
|
1623
|
+
- New props for phones: `Page` `safeArea`, `Modal` `fullScreen`, `SlideUp`
|
|
1624
|
+
`swipeToClose` and `footer`, `TopNavbar` `collapseAt`, `Tabs` `fullWidth`,
|
|
1625
|
+
`Tooltip` tap, `Select` and `Dropdown` `presentation`, `SidebarShell`
|
|
1626
|
+
`mobile="drawer"`, `Toaster` `--toaster-bottom-offset`. Components for them:
|
|
1627
|
+
AppShell, AppBar, BottomTabBar, BottomSheet, Drawer, StickyActionBar,
|
|
1628
|
+
FloatingActionButton, SwipeableListItem, PullToRefresh, PhotoViewer. Safe areas
|
|
1629
|
+
need `viewport-fit=cover`.
|
|
1630
|
+
|
|
1631
|
+
#### Accessibility
|
|
1632
|
+
|
|
1633
|
+
- A focus ring in forced colours: `.focus-ring`, the legacy `.focus-brand` and
|
|
1634
|
+
`.focus-nav`, and the checkbox and radio row draw an outline there. The
|
|
1635
|
+
current page has an outline in forced colours in SidebarNavigation,
|
|
1636
|
+
SidebarPanel and TopNavbar. In forced colours every option of a Select list
|
|
1637
|
+
has the same border and the chosen one is told apart by its check mark only.
|
|
1638
|
+
- `docs/ACCESSIBILITY.md` has a "Library conventions" section (where focus goes
|
|
1639
|
+
when a focused control is removed, the shared overlay stack and scroll lock,
|
|
1640
|
+
disabled options, hover-revealed actions, focus in forced colours) and no
|
|
1641
|
+
longer lists fixed issues as open; `docs/KEYBOARD_NAVIGATION.md` describes
|
|
1642
|
+
what the components do.
|
|
1643
|
+
|
|
1644
|
+
#### Strings and hydration
|
|
1645
|
+
|
|
1646
|
+
- **`ZabiStringsProvider`**: wrap the app in `<ZabiStringsProvider
|
|
1647
|
+
strings={…}>` and every component under it uses those words unless an
|
|
1648
|
+
instance says otherwise. One entry per component with a `strings` object
|
|
1649
|
+
(including `avatarGroup`, `swipeableListItem`, `pullToRefresh`), keyed by prop
|
|
1650
|
+
name for components with single-text props (`toast`, `alert`, `codeBlock`,
|
|
1651
|
+
`imageUpload`, `unsavedChangesBar`), and a `common` group (close, back, expand,
|
|
1652
|
+
collapse, required, showPassword, search, confirm, cancel). Order, later wins:
|
|
1653
|
+
built-in English, provider, the instance's `strings`, its own text props. It
|
|
1654
|
+
is Svelte context (per request on the server, nestable, reaches portalled
|
|
1655
|
+
overlays); Modal, Drawer, SlideUp, BottomSheet, Toaster, Rating and the
|
|
1656
|
+
components above read it; landmark `ariaLabel`s are not covered.
|
|
1657
|
+
`getZabiStrings()`, `DEFAULT_ZABI_COMMON_STRINGS` and the types are exported.
|
|
1658
|
+
No translations ship; the README has a complete Swedish example. Nothing
|
|
1659
|
+
changes outside a provider.
|
|
1660
|
+
- **Every built-in text can be replaced**: `strings` on TopNavbar,
|
|
1661
|
+
SidebarFooter, SidebarAccountPanel, SidebarNavigation, SidebarBrandHeader,
|
|
1662
|
+
ColorPicker, ContactForm, PropsTable, ComponentDemo, Select, Toaster, Stepper,
|
|
1663
|
+
Calendar, MediaGrid, SortableList, Rating, PullToRefresh. The README's "Texts
|
|
1664
|
+
and other languages" lists the prop for each component.
|
|
1665
|
+
- **A choice made before the page hydrates is kept.** A Checkbox, Radio,
|
|
1666
|
+
RadioGroup, Rating, SegmentedControl or Select changed in the server's markup
|
|
1667
|
+
was set back at hydration; the bound value now follows the native input and
|
|
1668
|
+
the change callback fires once (a form reset still returns to the rendered
|
|
1669
|
+
value). A bound Select value with no matching option is kept and submitted as
|
|
1670
|
+
itself.
|
|
1671
|
+
- **No component drops keyboard focus on hydration** (Heading, Text,
|
|
1672
|
+
CardHeader, AppBar's title, EmptyState's heading, Container, AppShell,
|
|
1673
|
+
Collapsible); a guard fails the build if a dynamic element comes back.
|
|
1674
|
+
|
|
1675
|
+
#### Package and build
|
|
1676
|
+
|
|
1677
|
+
- Exports and package fields are listed under Upgrade notes. `zabi-components/types`
|
|
1678
|
+
matches the components again (see Fixed). `npm run build:css` works in a fresh
|
|
1679
|
+
checkout with no `dist/`.
|
|
1680
|
+
- The docs site honours `data-theme`, and Storybook's toolbar Theme control
|
|
1681
|
+
drives its sidebar, toolbar and docs pages. `docs/theme-imports.md` says what
|
|
1682
|
+
to expect when Tailwind and `zabi-components/css` are combined (the compiled
|
|
1683
|
+
stylesheet carries its own copy of each utility, so import order decides: with
|
|
1684
|
+
Tailwind in the app import `theme-only` and `theme-dark-only` instead).
|
|
1685
|
+
`THEME.md` no longer tells apps to import the compiled stylesheet after
|
|
1686
|
+
`theme-only` or to use `theme()` for a colour. `THEME_QUICK_REFERENCE.md`
|
|
1687
|
+
shows the current primary colour and dark mirror; `docs/VARIANTS.md`, the
|
|
1688
|
+
README, the Storybook introduction and the showcase guide cover the new
|
|
1689
|
+
components. The component docs site examples are written in English. The
|
|
1690
|
+
site's catalog sidebar is a drawer below 1024px and its "On this page" list
|
|
1691
|
+
marks the section being read; the site sets `viewport-fit=cover`.
|
|
1692
|
+
|
|
1693
|
+
### Changed
|
|
1694
|
+
|
|
1695
|
+
- **Light mode has a visible surface ladder.** `--color-surface-base`,
|
|
1696
|
+
`--color-page` and `--color-background-tertiary` move from `base-100` to
|
|
1697
|
+
`base-150`; `--color-surface-elevated` from `base-50` to `base-100`; a card is
|
|
1698
|
+
1.18:1 on the page (was 1.10) and a nested card 1.10:1 (was 1.04).
|
|
1699
|
+
`--color-card-active` and `--color-surface-overlay-hover` are `base-150`.
|
|
1700
|
+
- **Light tinted fills read on a white card.** `--color-<family>-subtle` moves
|
|
1701
|
+
from step 100 to 200 and `--color-<family>-border` from 200 to 300 for
|
|
1702
|
+
success, warning, error, energetic and info (1.36:1 on a card, was 1.16);
|
|
1703
|
+
`--color-neutral-subtle` is `base-250`, `--color-neutral-border` `base-300`;
|
|
1704
|
+
`--color-action-primary-subtle` and `-subtle-hover` are `brand-200` and
|
|
1705
|
+
`brand-300`.
|
|
1706
|
+
- **Light hover and secondary fills are stronger.** `--color-surface-hover` and
|
|
1707
|
+
`--color-surface-active` are 9% and 15% ink (were 6% and 11%);
|
|
1708
|
+
`--color-action-secondary`, `-hover`, `-active` are 10%, 15%, 20% (were 7%,
|
|
1709
|
+
12%, 17%).
|
|
1710
|
+
- **Overlays have an edge in light** (`--color-border-overlay` is a 10% ink tint,
|
|
1711
|
+
was transparent); **light shadows are stronger** (`--shadow-color` `24 24 27`,
|
|
1712
|
+
`--shadow-opacity` 0.14; were `0 0 0` and 0.1).
|
|
1713
|
+
- **Light fields and disabled controls follow the new page**:
|
|
1714
|
+
`--color-input-hover` `base-100`, `--color-input-disabled` the page colour,
|
|
1715
|
+
`--color-action-disabled` `base-250`, `--color-action-disabled-border`
|
|
1716
|
+
`base-300`.
|
|
1717
|
+
- **Focus tokens**: `--color-focus` is `brand-600` in both themes;
|
|
1718
|
+
`--color-focus-weak`, `-medium`, `-strong` each move one step;
|
|
1719
|
+
`--color-nav-menu-focus` follows `--color-focus`; `.focus-ring--muted` (ghost
|
|
1720
|
+
and link buttons, the AppBar back control, the SortableList handle) reads
|
|
1721
|
+
`--color-focus-ring-muted`, 3.7:1 or more on every surface level (dark was
|
|
1722
|
+
`base-500`, 2.49:1 on elevated, 1.98:1 on overlay). Dark mode otherwise keeps
|
|
1723
|
+
its values: `.dark` restates the values it used to inherit.
|
|
1724
|
+
- **Dark mapping**: `--color-action-primary-text` follows `--color-on-brand`;
|
|
1725
|
+
dark `--color-base-*` steps alias the mirrored `--zabi-base-*` step; the dark
|
|
1726
|
+
block holds no raw palette and no hex value.
|
|
1727
|
+
- **Menus share one edge and surface**: Dropdown, NavigationMenuContent and the
|
|
1728
|
+
ColorPicker popover use `border-border-overlay` on `bg-surface-overlay` (the
|
|
1729
|
+
ColorPicker popover no longer sits on the inset field colour in dark). Dropdown
|
|
1730
|
+
menus and Select lists use the 16px overlay radius, and Select's search field
|
|
1731
|
+
and options sit 4px closer to the list's edge. ColorPicker's popover and
|
|
1732
|
+
NavigationMenu's panels use the overlay radius.
|
|
1733
|
+
- **Nested corners are concentric** where a rounded element sits closer to its
|
|
1734
|
+
container's corner than that corner's radius: the Card in a Modal, the avatar
|
|
1735
|
+
in SidebarFooter's profile button, MediaGrid's check mark (a rounded square)
|
|
1736
|
+
and video badge, Alert's close button, ListItem rows in a `.list-group`.
|
|
1737
|
+
- Input, Textarea and Select text is 16px below 640px (also ColorPicker's hex
|
|
1738
|
+
field and the search fields in Select and the sidebars); sizes unchanged from
|
|
1739
|
+
`sm` up and control heights unchanged.
|
|
1740
|
+
- **Checked checkboxes and radios use the primary action fill**, tick and dot in
|
|
1741
|
+
the primary button's text colour (was a paler step at 2.8:1 on the light page).
|
|
1742
|
+
- Input, Select and Textarea darken their border on hover in light
|
|
1743
|
+
(`--color-input-border-hover`); error, success, warning and disabled fields
|
|
1744
|
+
are unchanged. Select's trigger placeholder uses the placeholder colour.
|
|
1745
|
+
- Table and PropsTable sit on the card surface with an elevated header band;
|
|
1746
|
+
Skeleton and Header's variant chips use `bg-neutral-subtle`.
|
|
1747
|
+
- Sidebar search fields, the selected panel item, the elevated panel and the
|
|
1748
|
+
profile button use full-strength rings instead of 40–80% alpha ones; dashed
|
|
1749
|
+
empty states keep only their dashed border. SidebarNavigation's search
|
|
1750
|
+
placeholder keeps 4.5:1 on hover.
|
|
1751
|
+
- **Controls are 44px on touch** (`@media (pointer: coarse)`): Button, IconButton,
|
|
1752
|
+
Input and the Select trigger at `sm` and `md` (`lg` is 48px already; use it
|
|
1753
|
+
for the main action on a phone), and Slider rows, Checkbox, Radio and Toggle
|
|
1754
|
+
rows, Tabs, Collapsible triggers, Dropdown items, Select options (48px),
|
|
1755
|
+
NavigationMenu, TopNavbar and Sidebar items, SortableList controls and toast
|
|
1756
|
+
buttons. The close buttons of Alert, Toast, Modal, SlideUp and Drawer and the
|
|
1757
|
+
Toggle switch keep their size and take taps in a 44px area. IconButton `xs`
|
|
1758
|
+
stays 24px.
|
|
1759
|
+
- **Every pressable control shows a `:active` state**; a disabled Toggle or Tab
|
|
1760
|
+
no longer shows one. A toggled-on ghost, outline, link or danger-tone
|
|
1761
|
+
IconButton shows a pressed fill distinct from hover and 1.25:1 or more from
|
|
1762
|
+
rest. The selected pill Tab's pressed fill is 1.42:1 from rest (was 1.24:1)
|
|
1763
|
+
and its label keeps 4.89:1.
|
|
1764
|
+
- SlideUp pads its content for the bottom safe area and is at most `90dvh`
|
|
1765
|
+
(was `90vh`); Modal is at most `90dvh`.
|
|
1766
|
+
- **Tabs scroll sideways when they do not fit**, with a fade on the edge that has
|
|
1767
|
+
more, and bring the selected and keyboard-focused tab into view; labels no
|
|
1768
|
+
longer wrap or squeeze.
|
|
1769
|
+
- **Toasts**: sit above the tab bar and home indicator (the stack clears
|
|
1770
|
+
`--app-shell-bottom-inset` and the bottom safe area; below `sm` it spans the
|
|
1771
|
+
width with 16px gutters; a `viewport` Toast at the top clears the top safe
|
|
1772
|
+
area and an AppShell header); keep clear of an overlay's footer (16px above
|
|
1773
|
+
it, or at the top when there is no room; a centred desktop Modal changes
|
|
1774
|
+
nothing); take the larger free stretch above the panel or between header and
|
|
1775
|
+
footer, scrolling there, without covering an overlay's title and close button;
|
|
1776
|
+
sit above the on-screen keyboard; `--toaster-bottom-offset` is not added on top
|
|
1777
|
+
of an overlay's footer. Tab moves from an overlay's last control to the toast
|
|
1778
|
+
controls and back, and Escape in a toast returns focus without closing the
|
|
1779
|
+
overlay. Padding, gaps and button targets are in px and the status icon stays
|
|
1780
|
+
20px at enlarged text (a two-sentence toast at 375px and 200% text is at most
|
|
1781
|
+
420px tall, was 594px); buttons wrap under the title when it would get less
|
|
1782
|
+
than 8rem; the stack is capped to the screen and scrolls. The countdown says
|
|
1783
|
+
"1 second", not "1 seconds".
|
|
1784
|
+
- **Tooltip**: flips or slides to stay 8px inside the viewport, also with a
|
|
1785
|
+
mouse; below 640px a `left` or `right` tooltip opens above or below; a tap
|
|
1786
|
+
opens it when the finger comes up where it went down, so a scroll that starts
|
|
1787
|
+
on the trigger no longer flashes it; `data-placement` reports the side used.
|
|
1788
|
+
- **Overlays stay above the on-screen keyboard** where it covers the page; the
|
|
1789
|
+
focused field is scrolled into view.
|
|
1790
|
+
- **Overlay headers stay compact at large text sizes.** In BottomSheet, Modal,
|
|
1791
|
+
Drawer and SlideUp the header's padding, gaps, grip and close button are in px
|
|
1792
|
+
and the title grows to 1.3 times and stops; at 200% text a half-height sheet
|
|
1793
|
+
gives the content 378 of 576px (was 182). Titles break at a hyphen in the
|
|
1794
|
+
page's language and a too-long word no longer sticks out of Drawer or SlideUp.
|
|
1795
|
+
- **Select fills its container** and no longer changes width with the chosen
|
|
1796
|
+
option; its trigger is a `combobox` reporting `aria-invalid` (with `error`, the
|
|
1797
|
+
error variant or a failed `required` check), `aria-required` and `aria-busy`;
|
|
1798
|
+
`required` blocks an empty form, shows the browser's message as the error and
|
|
1799
|
+
focuses the trigger; a long label wraps; the form control is a `<select
|
|
1800
|
+
name>`; a reset returns it to its rendered value; it opens as a sheet on a
|
|
1801
|
+
phone; the chevron and the Dropdown panel do not animate under reduced
|
|
1802
|
+
motion; keyboard focus in the native select moves to the trigger at handover;
|
|
1803
|
+
`aria-label` and `aria-labelledby` name the native select too; with several
|
|
1804
|
+
empty required Selects only the form's first invalid control takes focus.
|
|
1805
|
+
- **A loading Button or IconButton** is `aria-disabled` and `aria-busy` (see
|
|
1806
|
+
Upgrade notes); a loading link keeps its place in the Tab order. Button labels
|
|
1807
|
+
wrap (`min-height`: 32, 40, 48px; 44px minimum on touch).
|
|
1808
|
+
- **A disabled Dropdown option stays focusable** (`aria-disabled`).
|
|
1809
|
+
- **A toast with an action stays until dismissed** unless given a `duration`;
|
|
1810
|
+
with no duration a toast closes after 7 s (see Upgrade notes); a message with
|
|
1811
|
+
no title is the toast's text.
|
|
1812
|
+
- **ThemeToggle** (two modes) has the name "Dark mode" and applies the stored
|
|
1813
|
+
choice on mount in both modes; it no longer writes an inline `color-scheme`
|
|
1814
|
+
and removes a stale one, so adding or removing `class="dark"` from your own
|
|
1815
|
+
script moves native controls with the tokens.
|
|
1816
|
+
- **EmptyState at `size="compact"`** is a plain `<div>`.
|
|
1817
|
+
- **ImageUpload** fills its container; `id` names the control; Change and Remove
|
|
1818
|
+
appear on `:focus-visible` (not any focus), wrap and truncate in a narrow
|
|
1819
|
+
container, stay visible on coarse pointers and where hover is unavailable, and
|
|
1820
|
+
sit on an opaque plate so "Change" is readable over any image (it was 2.59:1 in
|
|
1821
|
+
dark over a light image).
|
|
1822
|
+
- **`font-sans` and `font-mono` follow the theme.**
|
|
1823
|
+
- **A List row with nothing to do is plain content**; a disabled plain ListItem
|
|
1824
|
+
row no longer carries `aria-disabled`.
|
|
1825
|
+
- Checkbox and Radio inputs are stretched invisibly over their box.
|
|
1826
|
+
- Textarea and Select status messages use the same text colour step as Input's.
|
|
1827
|
+
- **Icons are imported one file at a time** from `@lucide/svelte/icons/<name>`,
|
|
1828
|
+
not the `@lucide/svelte` barrel: an app importing one Button from the root
|
|
1829
|
+
compiles 366 modules where it compiled 3,800 (233 from
|
|
1830
|
+
`zabi-components/atoms`). The re-exported icons keep their names.
|
|
1831
|
+
- The `viewport` prop on NavigationMenu only sets `isMobile` on the context for
|
|
1832
|
+
your own children; it has no effect of its own, and the docs now say so.
|
|
1833
|
+
- **`Toast`/`Alert`/`CodeBlock`/`ImageUpload`/`UnsavedChangesBar`** follow
|
|
1834
|
+
`ZabiStringsProvider`.
|
|
1835
|
+
|
|
1836
|
+
### Deprecated
|
|
1837
|
+
|
|
1838
|
+
- **Modal's `onclick` is deprecated as a close signal.** It is still called with
|
|
1839
|
+
the event on Escape, a backdrop click and the close button; use `onclose`.
|
|
1840
|
+
- In `zabi-components/types`, members that were exported but never existed
|
|
1841
|
+
(`open`, `className`, `position`, Button's `ariaLabel` and icon props) stay as
|
|
1842
|
+
optional and deprecated so existing code keeps compiling.
|
|
1843
|
+
|
|
1844
|
+
### Fixed
|
|
1845
|
+
|
|
1846
|
+
- **`strings` props and `ZabiStringsProvider`:** a key set to `undefined` keeps
|
|
1847
|
+
the English default instead of blanking the text (`null` and an empty string
|
|
1848
|
+
are still honoured).
|
|
1849
|
+
- **`Progress`:** a passed `id` is on the wrapper only (it appeared on the
|
|
1850
|
+
wrapper and the bar); `aria-valuenow` is clamped to 0..`max`; a non-positive
|
|
1851
|
+
or non-finite `max` falls back to 100; new `aria-label` and `aria-labelledby`
|
|
1852
|
+
props name the progressbar.
|
|
1853
|
+
- **`ColorPicker`:** Escape closes the popover and returns focus to the swatch
|
|
1854
|
+
(and does not close a Modal around it); opening by keyboard moves focus in;
|
|
1855
|
+
typing a hex redraws the colour map; with a `label`, the hex input and swatch
|
|
1856
|
+
are named by it. Its popover could be placed below the viewport at 320px; a
|
|
1857
|
+
press on a toast no longer closes it.
|
|
1858
|
+
- **`Select`:** the popover listbox is named by the field's label (still "Select
|
|
1859
|
+
options" with no label); **`Dropdown`** gets an optional `ariaLabelledby`
|
|
1860
|
+
prop. Arrow keys get past a disabled option. The Select trigger's pressed
|
|
1861
|
+
state shows in light (was the resting fill) and is 1.27:1 against rest in
|
|
1862
|
+
light and 1.31:1 in dark (was 1.10:1). A Select no longer changes its value
|
|
1863
|
+
when the page hydrates. A disabled Select dims its value. Dropdown and Select
|
|
1864
|
+
return focus to the trigger when the menu closes from inside it; focus your
|
|
1865
|
+
own handler moved elsewhere is left alone. A Select list and a Dropdown menu
|
|
1866
|
+
are one Tab stop: Shift+Tab goes to the search field in one step, and a
|
|
1867
|
+
character typed on an option goes to the search field or the matching item.
|
|
1868
|
+
A press on a toast no longer closes an open Select or Dropdown.
|
|
1869
|
+
- **`SwipeableListItem`:** a long action label wraps (it was clipped at the start
|
|
1870
|
+
at 200% text), the action area is capped at two thirds of the row, and the
|
|
1871
|
+
"more" button stays 44px at any text size. With `showMoreButton={false}` focus
|
|
1872
|
+
has nowhere to return to after an action.
|
|
1873
|
+
- A long Modal title no longer squeezes the close button or runs out of the
|
|
1874
|
+
panel (the title wraps, also inside a single long word; the close button keeps
|
|
1875
|
+
32px).
|
|
1876
|
+
- A Tooltip with `strategy="fixed"` no longer covers its trigger when it opens
|
|
1877
|
+
above or to the left; a closed tooltip no longer widens the page; it has an
|
|
1878
|
+
edge in forced colours, is centred correctly in RTL and its arrow points at its
|
|
1879
|
+
trigger in RTL; only one tooltip is open at a time and the pointer can cross a
|
|
1880
|
+
diagonal to its far corner. Escape with a tooltip showing dismisses the
|
|
1881
|
+
tooltip only, not the modal or sheet it sits in.
|
|
1882
|
+
- A Dropdown stays on screen: measured before paint, it flips when the preferred
|
|
1883
|
+
side does not fit, slides back when neither does and is capped to the viewport
|
|
1884
|
+
with its own scroll only when it must be (8px margin). `placement` is still
|
|
1885
|
+
the preferred side; a menu that fits is positioned as before. A Dropdown, Select
|
|
1886
|
+
list or NavigationMenu panel is no longer cut off by a scrolling or clipping
|
|
1887
|
+
ancestor such as a Modal's content (the nearest clipping ancestor decides the
|
|
1888
|
+
side; when it fits on neither it is positioned against the viewport; an
|
|
1889
|
+
ancestor with a transform or filter cannot be escaped), and a menu leaves a
|
|
1890
|
+
clipping container only when that puts it on screen. A height-capped Dropdown
|
|
1891
|
+
scrolls inside its rounded panel. Dropdown follows the writing direction
|
|
1892
|
+
(`bottom-start` and `top-start` sit on the start edge, item text aligns to it).
|
|
1893
|
+
- NavigationMenu fits a narrow screen: the list wraps onto further rows and a
|
|
1894
|
+
content panel slides back inside the viewport. A press on a toast no longer
|
|
1895
|
+
closes a NavigationMenu panel or the TopNavbar menu.
|
|
1896
|
+
- TopNavbar's phone menu closes after a link in it is followed, when
|
|
1897
|
+
`currentPath` changes, on Escape (focus returns to the menu button), on a click
|
|
1898
|
+
or focus move outside the bar and when the screen widens past the breakpoint;
|
|
1899
|
+
a press on a control behind the open menu still reaches it. It is at most as
|
|
1900
|
+
tall as the screen below the bar and scrolls, allows for the top safe area,
|
|
1901
|
+
its links fill the row, and `aria-controls` on the menu button is set only
|
|
1902
|
+
while the menu exists. The menu button stays on screen with a long brand or
|
|
1903
|
+
enlarged text (the brand truncates below the breakpoint).
|
|
1904
|
+
- Toasts fit a 320px screen (the 18rem minimum width no longer overflows); the
|
|
1905
|
+
close button takes taps in its full 44px area; `aria-controls` on the expand
|
|
1906
|
+
button is set only while the detail is open; focus no longer falls to the page
|
|
1907
|
+
after "Okay" or "Click to stop" is pressed; dismissing the focused toast moves
|
|
1908
|
+
focus to the next toast or back to where it was; a tap neither sticks nor
|
|
1909
|
+
dismisses; toasts do not fly in or out under `prefers-reduced-motion`.
|
|
1910
|
+
- A disabled Checkbox, Radio or RadioGroup row no longer dips, changes fill or
|
|
1911
|
+
shows the pressed fill when pressed or hovered; stacked checkbox and radio
|
|
1912
|
+
rows no longer overlap on touch; the row shows its hover
|
|
1913
|
+
(`--color-surface-hover`, `--color-surface-active`, was a fixed `base-100`).
|
|
1914
|
+
Checkbox shows its loading ring (replacing the tick, fading under reduced
|
|
1915
|
+
motion).
|
|
1916
|
+
- Alert's close button shows the library's focus ring and mirrors in RTL.
|
|
1917
|
+
- State variants beside a semantic colour class work: the hand-written colour
|
|
1918
|
+
classes sit outside every cascade layer, so `text-description
|
|
1919
|
+
hover:text-headline` never changed on hover. Thirteen such variants the
|
|
1920
|
+
components use are restated by hand (the hover colour of close buttons, the
|
|
1921
|
+
hover border of outline buttons, the disabled text colour of fields and ghost
|
|
1922
|
+
buttons).
|
|
1923
|
+
- Accent hover and pressed states work: `hover:bg-accent-hover` and
|
|
1924
|
+
`active:bg-accent-active` did nothing next to `bg-accent`.
|
|
1925
|
+
- An overlay opened later is always on top: Modal and SlideUp take a z-index
|
|
1926
|
+
that grows with the number of open overlays, so a modal opened from a portalled
|
|
1927
|
+
one no longer opens behind it; a modal opened while a portalled drawer is open
|
|
1928
|
+
(and the reverse) is drawn on top.
|
|
1929
|
+
- Modal and SlideUp keep Tab inside, and still close on Escape, when focus has
|
|
1930
|
+
fallen out of the dialog because the focused control was disabled or removed.
|
|
1931
|
+
A modal or sheet with nothing focusable takes focus itself. Modal, Drawer,
|
|
1932
|
+
SlideUp and BottomSheet no longer take focus from content that focused itself
|
|
1933
|
+
inside the panel as it opened. Focus returns to a replaced element (the one
|
|
1934
|
+
that now has the opener's id) instead of `<body>`. Modal and SlideUp do not
|
|
1935
|
+
slide in under `prefers-reduced-motion`.
|
|
1936
|
+
- The focus ring survives a shadow: `.focus-ring` on an element with a `shadow-*`
|
|
1937
|
+
or `ring-*` utility (also `shadow-none`) drew no ring (the SidebarNavigation
|
|
1938
|
+
search field, the selected SidebarPanel row, an interactive Card, any
|
|
1939
|
+
IconButton given a shadow); it now composes. A List no longer clips the focus
|
|
1940
|
+
ring of its rows.
|
|
1941
|
+
- Placeholder contrast: placeholder text and the format hint of an empty
|
|
1942
|
+
DateField or TimeField was 3.40:1 on a dark field and 4.40:1 on a hovered light
|
|
1943
|
+
one; see Upgrade notes. A disabled field's placeholder is no stronger than a
|
|
1944
|
+
disabled value.
|
|
1945
|
+
- Native controls follow the dark theme under `.dark` as well as `data-theme`
|
|
1946
|
+
(scrollbars, date and time pickers, other native form controls, autofill); a
|
|
1947
|
+
page that sets `color-scheme` on `<html>` keeps its value.
|
|
1948
|
+
- Hover colours no longer stick after a tap: the hand-written `:hover` rules in
|
|
1949
|
+
the theme files (`.bg-action-primary:hover` and 41 more) now sit in `@media
|
|
1950
|
+
(hover: hover)`.
|
|
1951
|
+
- ImageUpload keeps keyboard focus (to Change after a selection, to the dropzone
|
|
1952
|
+
after Remove, untouched when focus is elsewhere), cancels a file dropped while
|
|
1953
|
+
`disabled`, its actions layer no longer takes clicks meant for the preview, its
|
|
1954
|
+
error message is tied to the Change button when a preview is showing, and a
|
|
1955
|
+
disabled dropzone no longer takes the primary border on hover.
|
|
1956
|
+
- ActionPanel shows its hover and pressed states (the fill is drawn over the
|
|
1957
|
+
card). A selected Tabs pill shows its pressed state and a selected List row its
|
|
1958
|
+
selected border. ListItem rows show a hover and a pressed fill.
|
|
1959
|
+
SidebarFooter's avatar ring used a colour token that does not exist and now uses
|
|
1960
|
+
the border colour. A `dark:` variant in Section's accent background, which
|
|
1961
|
+
followed the OS setting and never applied, was removed.
|
|
1962
|
+
- ThemeToggle shows the right icon before it mounts (both icons are in the
|
|
1963
|
+
markup and the `dark` class picks one); it works with `data-theme`.
|
|
1964
|
+
- ConfirmDialog no longer draws a focus outline around the whole dialog while
|
|
1965
|
+
loading and sets no `aria-busy` on the panel.
|
|
1966
|
+
- A Badge with a long label wraps: it is at least 20, 24 or 28px tall, grows when
|
|
1967
|
+
the label wraps and is never wider than its container; a one-line badge is
|
|
1968
|
+
unchanged to the pixel and a wrapped one reads as a rounded rectangle.
|
|
1969
|
+
- An AppBar's title is never squeezed out (see AppBar).
|
|
1970
|
+
- Keyboard focus is visible in forced colours (see Accessibility).
|
|
1971
|
+
- **`zabi-components/types` matches the components again.** Every exported
|
|
1972
|
+
`*Props` interface declares the props its component accepts today: `ModalProps`
|
|
1973
|
+
has `isOpen`, the interfaces have `class`, and the new props are there.
|
|
1974
|
+
`SelectProps.options`, `AlertProps.message` and `TooltipProps.content` are
|
|
1975
|
+
optional, as on the components.
|
|
1976
|
+
- A bindable prop bound to `undefined` no longer stops the page from hydrating
|
|
1977
|
+
(RadioGroup threw, and 36 other bindable props could); see Upgrade notes.
|
|
1978
|
+
- Button and IconButton (including `variant="accent"`) do not scale when pressed
|
|
1979
|
+
under `prefers-reduced-motion`; built-in loading rings (Button, IconButton,
|
|
1980
|
+
Input, Textarea, ActionPanel, Toggle) fade instead of spinning, Checkbox's
|
|
1981
|
+
ring stops; Toggle's loading ring has its gap back.
|
|
1982
|
+
- Input, Textarea and Select no longer point `aria-describedby` at a message that
|
|
1983
|
+
is not rendered.
|
|
1984
|
+
- `fullWidth` Tabs scroll instead of breaking a word; BottomSheet's grip answers
|
|
1985
|
+
a mouse click; a flick on a sheet is measured from the events' own times.
|
|
1986
|
+
- Drawer content clears the home indicator; MediaGrid's remove button shows a
|
|
1987
|
+
pressed state on touch; SidebarAccountPanel's three-mode theme row follows a
|
|
1988
|
+
theme changed elsewhere; CodeBlock announces "copied".
|
|
1989
|
+
- The brand menu in the docs site header stays on screen (it opened past the
|
|
1990
|
+
right edge at 768px and 1024px); the Pine and Citron accents follow the Iris
|
|
1991
|
+
recipe in light (fill at step 600 with a white label, focus ring at step 600;
|
|
1992
|
+
Pine's light ring was 1.93:1 on the page, now 4.19:1); `theme-color` follows
|
|
1993
|
+
the theme toggle; the site's cards carry `shadow-sm` and the showcase uses
|
|
1994
|
+
surface tokens in place of raw ramp steps.
|
|
1995
|
+
- README links to RELEASING.md point at the repository, and the README says to
|
|
1996
|
+
install `@lucide/svelte` and import icons per file.
|
|
1997
|
+
|
|
1998
|
+
## [8.0.1] - 2026-09-29
|
|
1999
|
+
|
|
2000
|
+
Fixes the documented setup. In 8.0.0 (and 7.0.2) the three-line Tailwind setup
|
|
2001
|
+
from the README left components unstyled; `@import "zabi-components/css"` was
|
|
2002
|
+
the only import that worked. No tokens or `exports` paths were added, removed
|
|
2003
|
+
or renamed.
|
|
2004
|
+
|
|
2005
|
+
### Fixed
|
|
2006
|
+
|
|
2007
|
+
- **Theme files are valid CSS again.** `theme`, `theme-only`, `theme-dark` and
|
|
2008
|
+
`theme-dark-only` were written without semicolons between declarations, so a
|
|
2009
|
+
CSS parser read all 355 tokens as one declaration. `scripts/build-css.js` now
|
|
2010
|
+
terminates each declaration.
|
|
2011
|
+
- **`theme` and `theme-only` carry an `@source` directive** for the package's
|
|
2012
|
+
own components. Tailwind CSS v4 does not scan `node_modules`, so the classes
|
|
2013
|
+
the components use were never generated. No `@source` line is needed in your
|
|
2014
|
+
own CSS.
|
|
2015
|
+
- **`theme` and `theme-only` ship the hand-written component rules**:
|
|
2016
|
+
`.focus-ring` and its variants, `.text-action-primary`, the action hover,
|
|
2017
|
+
active and disabled states, the semantic colour classes and the `z-*` scale.
|
|
2018
|
+
These existed only in the compiled `css` bundle. Without them a primary
|
|
2019
|
+
button's label took the colour of its fill, and nothing had a focus ring.
|
|
2020
|
+
- **ColorPicker renders on the server.** Its teardown ran in `onDestroy`, which
|
|
2021
|
+
also runs during SSR, and threw `window is not defined`. It is now returned
|
|
2022
|
+
from `onMount`.
|
|
2023
|
+
- **Progress has an accessible name.** Its label was a `<label for>`, which
|
|
2024
|
+
only names form controls, so screen readers announced a progressbar with no
|
|
2025
|
+
name. The label is now referenced through `aria-labelledby`.
|
|
2026
|
+
|
|
2027
|
+
### Changed
|
|
2028
|
+
|
|
2029
|
+
- The universal-selector scrollbar rules (`* { scrollbar-width: thin }` and the
|
|
2030
|
+
`*::-webkit-scrollbar` family) are in the compiled `css` bundle only, not in
|
|
2031
|
+
the theme files, so importing a theme does not restyle every scrollbar in
|
|
2032
|
+
your app. `.scrollbar-semantic` is in both and is the opt-in.
|
|
2033
|
+
- `theme` and `theme-only` grew from about 22 kB to about 35 kB.
|
|
2034
|
+
- `scripts/validate-theme.js` parses the theme files instead of pattern-matching
|
|
2035
|
+
them, and fails the build if the token block does not parse or the component
|
|
2036
|
+
rules are missing. The old check passed on the broken files.
|
|
2037
|
+
|
|
2038
|
+
### Migration
|
|
2039
|
+
|
|
2040
|
+
Nothing to change if you import `zabi-components/css`. If you worked around the
|
|
2041
|
+
broken theme files with your own `@source` line or your own copies of
|
|
2042
|
+
`.focus-ring` or `.text-action-primary`, you can remove them.
|
|
2043
|
+
|
|
2044
|
+
## [8.0.0] - 2026-09-22
|
|
2045
|
+
|
|
2046
|
+
Consolidates two development cycles. 7.1.0 and 7.2.0 were both prepared but
|
|
2047
|
+
never published, so the last release on npm is 7.0.2 and everything below ships
|
|
2048
|
+
together in 8.0.0.
|
|
2049
|
+
|
|
2050
|
+
### Breaking changes since 7.0.2
|
|
2051
|
+
|
|
2052
|
+
What can break a working 7.0.2 consumer. Each item is detailed in the sections
|
|
2053
|
+
that follow.
|
|
2054
|
+
|
|
2055
|
+
- **The `zabi-components/react` subpath is gone** from `exports`. It pointed at
|
|
2056
|
+
`dist/react`, which was never built, so importing from it already failed at
|
|
2057
|
+
resolution — but the subpath no longer exists at all.
|
|
2058
|
+
- **Three tokens were removed**: `--color-action-primary-disabled`,
|
|
2059
|
+
`--color-action-secondary-disabled`, `--color-action-danger-disabled`. Use the
|
|
2060
|
+
shared `--color-action-disabled`, `--color-action-disabled-text` and
|
|
2061
|
+
`--color-action-disabled-border` instead. Your own CSS referencing the old
|
|
2062
|
+
names will silently resolve to nothing, so grep for them before upgrading.
|
|
2063
|
+
- **Every chromatic ramp was regenerated** against a shared lightness curve, and
|
|
2064
|
+
the semantic families moved from step 500 to step 600. Rendered colour changes
|
|
2065
|
+
throughout, in both themes, with no opt-out. Screenshot tests will all churn.
|
|
2066
|
+
- **`max-w-*`, `w-*` and `min-w-*` with `xs`–`2xl` resolve to container sizes
|
|
2067
|
+
again** (`max-w-lg` is 32rem, not 1.5rem). This is a bug fix, but anyone who
|
|
2068
|
+
compensated for the broken values in their own CSS must undo that.
|
|
2069
|
+
- **No install scripts run in consumer installs.** The `postinstall` hook is
|
|
2070
|
+
gone; it is now a dev-only `prepare` script and is not in the tarball.
|
|
2071
|
+
|
|
2072
|
+
Not breaking, but worth knowing: the published tarball no longer contains
|
|
2073
|
+
`dist/lib/**`. Those were declaration files for this repo's demo site and
|
|
2074
|
+
showcase, emitted by mistake; no `exports` entry ever pointed at them.
|
|
2075
|
+
|
|
2076
|
+
### Design system
|
|
2077
|
+
|
|
2078
|
+
Outcome of a full design review of all 63 components. The headline change is
|
|
2079
|
+
that the colour ramps are now generated against one shared lightness curve, so
|
|
2080
|
+
the semantic families read as a single family instead of six unrelated colours.
|
|
2081
|
+
|
|
2082
|
+
#### Colour — BREAKING (visual)
|
|
2083
|
+
|
|
2084
|
+
- **All chromatic ramps regenerated** (`brand`, `citron`, `pine`, `iris`,
|
|
2085
|
+
`warning`, `error`) against a shared CIE L\* curve defined in
|
|
2086
|
+
`tokens/chromatic-scales.js`. `<ramp>-600` now means the same lightness in
|
|
2087
|
+
every ramp. Previously the ramps spanned L\* 34.6–75.0 at step 500 — a
|
|
2088
|
+
40-point spread at the same nominal step — and `pine` fell 29.8 points between
|
|
2089
|
+
400 and 500, which made half that ramp unusable.
|
|
2090
|
+
- **Semantic tokens moved to step 600** across the board. `warning`, `error` and
|
|
2091
|
+
`energetic` previously sat at step 500 (the dark mirror's fixed point) and so
|
|
2092
|
+
did not invert between themes at all, while `success` and `info` did. Solid
|
|
2093
|
+
badges were 2.15:1 (warning) and 1.97:1 (energetic) in light mode.
|
|
2094
|
+
- **New per-family tokens**: `--color-<family>-subtle` (step 100),
|
|
2095
|
+
`--color-<family>-border` (step 200). `--color-<family>-text` is now step 700
|
|
2096
|
+
for every family.
|
|
2097
|
+
- **`--color-action-primary` is now `brand-600`** (was `brand-950`). The primary
|
|
2098
|
+
action carries the brand colour in both themes.
|
|
2099
|
+
- **`--color-action-primary-active` no longer jumps across the ramp.** It was
|
|
2100
|
+
`brand-400` against a `brand-50` label — 1.66:1, so the label vanished while
|
|
2101
|
+
the button was held. Hover/active now step darker in light, lighter in dark.
|
|
2102
|
+
- **`--color-action-secondary`** is a neutral alpha tint instead of a 10% brand
|
|
2103
|
+
wash, which was too pale to read as a control.
|
|
2104
|
+
- **New**: `--color-action-disabled`, `--color-action-disabled-text`,
|
|
2105
|
+
`--color-action-disabled-border` — one quiet neutral pair shared by every
|
|
2106
|
+
variant. Disabled was previously `brand-500` + `opacity-50`, which made a
|
|
2107
|
+
disabled primary *lighter and more colourful* than an enabled secondary.
|
|
2108
|
+
- **New**: `--color-surface-hover`, `--color-surface-active` — surface-relative
|
|
2109
|
+
alpha tints. `hover:bg-base-100` resolved to exactly `--color-surface-base` in
|
|
2110
|
+
dark mode, so ghost buttons had no hover feedback on the app shell.
|
|
2111
|
+
- **New**: `--color-control-track{,-hover,-active}` for switch rails.
|
|
2112
|
+
- **Removed**: `--color-action-{primary,secondary,danger}-disabled` (use
|
|
2113
|
+
`--color-action-disabled`); the raw-hex `.dark` danger overrides (danger now
|
|
2114
|
+
mirrors through `--color-error-*` like every other family).
|
|
2115
|
+
- `--color-input` is now a raised surface (white in light, an inset step in
|
|
2116
|
+
dark) rather than a grey well that read as disabled.
|
|
2117
|
+
- `--color-input-placeholder` is wired up — it was defined but unused.
|
|
2118
|
+
- `--color-link` moved to `brand-700`: `brand-600` cleared AA on a card but only
|
|
2119
|
+
managed 4.42:1 on the page surface.
|
|
2120
|
+
|
|
2121
|
+
#### Geometry — BREAKING (visual)
|
|
2122
|
+
|
|
2123
|
+
- **One control height scale**: `sm` 32px, `md` 40px, `lg` 48px, shared by
|
|
2124
|
+
Button, IconButton, Input, Select and Textarea. They previously rendered at
|
|
2125
|
+
40/48/64, 32/40/48 and 38/46/50 — a `lg` Button stood 14px taller than the
|
|
2126
|
+
`lg` Input beside it.
|
|
2127
|
+
- **Four role-based radii** replace eight t-shirt radii: `--radius-control`
|
|
2128
|
+
(8px), `--radius-container` (12px), `--radius-overlay` (16px),
|
|
2129
|
+
`--radius-pill`. Radius no longer changes with size. `--radius-sm/md/lg/xl`
|
|
2130
|
+
remain as legacy aliases for consumer overrides.
|
|
2131
|
+
- Button type is monotonic again: weight is `font-medium` at every size (it used
|
|
2132
|
+
to drop to `font-normal` at `lg`, so the biggest button had the lightest
|
|
2133
|
+
label), and the arbitrary letter-spacing values are gone.
|
|
2134
|
+
- **Elevation is two steps**, not four. `shadow-sm` means raised (cards, tables,
|
|
2135
|
+
sidebars, toggle knobs), `shadow-lg` means floating (modals, sheets, menus,
|
|
2136
|
+
toasts); `shadow-none` stays the explicit absence of one. `shadow-md` and
|
|
2137
|
+
`shadow-xl` are gone, and so is the bare `shadow` on the Toggle knob, which
|
|
2138
|
+
resolved to a Tailwind default `app.css` never defined. An elevated Card no
|
|
2139
|
+
longer deepens its shadow on hover — it was already at the floating step, so
|
|
2140
|
+
the hover now changes background only.
|
|
2141
|
+
- **Layout spacing snaps to the 4px grid.** 30 half-step utilities
|
|
2142
|
+
(`gap-1.5`, `gap-2.5`, `px-2.5`, `px-3.5`, `py-2.5`, `space-y-1.5`, …) were
|
|
2143
|
+
setting a second rhythm against the first across forms, sidebars and badges.
|
|
2144
|
+
Margins are deliberately exempt: `mt-0.5` beside a first line of text is an
|
|
2145
|
+
optical alignment nudge, not rhythm, and Alert, ListItem, ToasterToast and the
|
|
2146
|
+
radio control still use it.
|
|
2147
|
+
- A one-row `Textarea` is now 40px like the `Input` beside it (was 44px, from
|
|
2148
|
+
`py-2.5`).
|
|
2149
|
+
|
|
2150
|
+
#### Components
|
|
2151
|
+
|
|
2152
|
+
- **`Badge`**: rebuilt. New `emphasis` prop (`subtle` | `solid`, default
|
|
2153
|
+
`subtle`), plus `class`, `children` and `...restProps` — none of which existed,
|
|
2154
|
+
despite stories shipping for two of them. Pill radius instead of 2px. Renders
|
|
2155
|
+
nothing when it has no label, rather than leaving an empty box.
|
|
2156
|
+
- **`Alert`**: tinted fill + tinted border instead of a bare 1px outline on a
|
|
2157
|
+
transparent ground; full-width by default with a new `inline` prop; one Lucide
|
|
2158
|
+
icon family instead of hand-rolled SVGs of mixed weight.
|
|
2159
|
+
- **`Heading`**: no longer hardcodes `font-family: "Nunito Sans"` in a local
|
|
2160
|
+
`<style>` block — it uses `--font-family-sans`, so rebranding the font token
|
|
2161
|
+
now actually rebrands headings. `level` is typed `1 | 2 | 3 | 4 | 5 | 6`
|
|
2162
|
+
instead of `number`; new `size` prop decouples visual size from semantic
|
|
2163
|
+
level; `children` snippet supported; display sizes carry negative tracking.
|
|
2164
|
+
- **New `SidebarShell`**: the chrome of a sidebar with the regions left open —
|
|
2165
|
+
width, surface, collapse behaviour, the shared inset and a scrolling middle.
|
|
2166
|
+
Regions are snippets (`header` / `children` / `footer`), each handed
|
|
2167
|
+
`{ collapsed, insetX }`. `SidebarNavigation` is 39 props on one component and
|
|
2168
|
+
is excellent right up until your sidebar is not shaped like that one, at
|
|
2169
|
+
which point none of the parts are reachable; compose `SidebarBrandHeader`,
|
|
2170
|
+
`SidebarNavSection` and `SidebarFooter` into the shell instead and skip the
|
|
2171
|
+
~30 props you never set. `SidebarNavigation` is now built on it rather than
|
|
2172
|
+
duplicating the chrome, so the two cannot drift. Additive — its prop API is
|
|
2173
|
+
unchanged.
|
|
2174
|
+
- **`SidebarNavigation`**: its Props table documented 7 of 39 props. All 39 are
|
|
2175
|
+
documented now, with types and defaults.
|
|
2176
|
+
- **`Text`**: now sits on the same type ramp as `Heading` rather than carrying
|
|
2177
|
+
its own — `size="md"` matches an `h6` and `size="lg"` an `h5`, line box
|
|
2178
|
+
included, and a new `xs` step (12px) extends the bottom. Leading is stated
|
|
2179
|
+
rather than inherited from Tailwind's defaults, so the shared steps stay
|
|
2180
|
+
locked together if the scale is retuned. **New `weight` prop** (`normal` |
|
|
2181
|
+
`medium` | `semibold` | `bold`) lets body copy carry emphasis without being
|
|
2182
|
+
promoted to a heading; `tone="label"` defaults to `medium`, every other tone
|
|
2183
|
+
to `normal`. Purely additive — existing `sm`/`md`/`lg` call sites are
|
|
2184
|
+
unchanged visually.
|
|
2185
|
+
- **`Input`**: raised surface, quieter placeholder, `min-w-48` removed.
|
|
2186
|
+
- **`Card`**: radius no longer scales with size; `outlined` uses `border-border`.
|
|
2187
|
+
|
|
2188
|
+
#### API consistency
|
|
2189
|
+
|
|
2190
|
+
- **Class overrides actually win now.** `class` being "merged last" only held
|
|
2191
|
+
for properties the component didn't already set: `rounded-control` and
|
|
2192
|
+
`rounded-container` are equal-specificity utilities, so the winner was
|
|
2193
|
+
whichever Tailwind emitted later. The sidebar's search field asked for
|
|
2194
|
+
`rounded-container` and rendered at 8px. Every component now merges through
|
|
2195
|
+
`cn()` (tailwind-merge, a new runtime dependency), with the role-based radii
|
|
2196
|
+
declared explicitly since `control`/`container`/`overlay`/`pill` are role
|
|
2197
|
+
names rather than scale values that tailwind-merge could recognise.
|
|
2198
|
+
- **`class` is now the public prop on every component.** 25 components exposed
|
|
2199
|
+
`className`, 15 exposed `class`, and 21 exposed neither. `className` is kept
|
|
2200
|
+
everywhere as a deprecated alias and both are merged, so existing call sites
|
|
2201
|
+
keep working.
|
|
2202
|
+
- Every component spreads `...restProps` onto its host element.
|
|
2203
|
+
- `Button`: `fullWidth` replaces `isFullWidth` (deprecated alias retained).
|
|
2204
|
+
|
|
2205
|
+
#### Guardrails
|
|
2206
|
+
|
|
2207
|
+
- **New** `scripts/check-ramp-lightness.js` — fails if a ramp drifts off the
|
|
2208
|
+
shared curve (±1.5 L\*) or develops a cliff (>15 L\* between adjacent steps).
|
|
2209
|
+
- **New** `scripts/check-contrast.js` — resolves every fill/foreground pair a
|
|
2210
|
+
component can render, in both themes, and fails below WCAG AA. 84 pairs.
|
|
2211
|
+
- **New** `scripts/check-control-geometry.js` — fails if controls disagree on
|
|
2212
|
+
height, or if any component uses a t-shirt radius.
|
|
2213
|
+
- **New** `scripts/generate-ramps.js` + `tokens/chromatic-scales.js` — ramp
|
|
2214
|
+
generation, wired into `npm run sync:tokens`.
|
|
2215
|
+
- `scripts/check-token-violations.js` now also rejects fixed ramp steps in
|
|
2216
|
+
interaction states (`hover:bg-base-*`, `active:bg-base-*`), which is how the
|
|
2217
|
+
invisible ghost hover got in.
|
|
2218
|
+
- **New** `scripts/generate-surfaces.js` + `tokens/surface-ladder.js` — the four
|
|
2219
|
+
dark surface levels are now derived from one wash ladder instead of four
|
|
2220
|
+
independently tuned hex values, and regenerate with `npm run sync:tokens`.
|
|
2221
|
+
`--color-surface-elevated` moves `#35353a` → `#363638` and
|
|
2222
|
+
`--color-surface-overlay` `#44444c` → `#454547` (slightly less blue); base and
|
|
2223
|
+
raised were already exactly on the ladder.
|
|
2224
|
+
- The ramp-interaction rule now also scans `src/lib/marketing` and the landing
|
|
2225
|
+
route. The rest of the checks guard what ships in the package, but
|
|
2226
|
+
`hover:bg-base-100` had reached two marketing specimens, where dark mode
|
|
2227
|
+
painted the hover darker than the card it sat on.
|
|
2228
|
+
- `scripts/check-token-violations.js` also rejects **off-scale shadows**
|
|
2229
|
+
(anything but `shadow-sm` / `shadow-lg` / `shadow-none`, including a bare
|
|
2230
|
+
`shadow`) and **half-step spacing** on gap/space/padding utilities. Margins
|
|
2231
|
+
are exempt by design. Comment lines are skipped so prose about shadow DOM
|
|
2232
|
+
doesn't trip it.
|
|
2233
|
+
- New scripts: `npm run check:ramps`, `check:contrast`, `check:geometry`, and
|
|
2234
|
+
`check:design` (all four). `build:css` and `check` run `check:design`.
|
|
2235
|
+
|
|
2236
|
+
#### Stories
|
|
2237
|
+
|
|
2238
|
+
- `Badge` → `AllVariantsWithIcons` no longer crashes. It used an invalid CSF
|
|
2239
|
+
render shape (`Component: 'div'` with a `children` array) cast through
|
|
2240
|
+
`as any`, so TypeScript could not catch it and it threw at runtime. Replaced
|
|
2241
|
+
with real harness components, which also fixes `WithChildren` and
|
|
2242
|
+
`WithCustomClass` (both silently rendered nothing).
|
|
2243
|
+
|
|
2244
|
+
### Forms
|
|
2245
|
+
|
|
2246
|
+
- **`ContactForm`**: invalid submissions no longer fall through to a native POST and page reload.
|
|
2247
|
+
- **`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.
|
|
2248
|
+
- **`ColorPicker`**: 3-digit hex (`#f00`) is parsed correctly; `value` supports `bind:value`.
|
|
2249
|
+
- **`Form`**: accepts standard form attributes and a `novalidate` prop; docs note that `onsubmit` handlers must call `preventDefault()` for client-side handling.
|
|
2250
|
+
|
|
2251
|
+
### Widgets
|
|
2252
|
+
|
|
2253
|
+
- **`Tabs`**: Arrow/Home/End move focus as well as selection; the tablist is no longer an extra tab stop; `activeTab` supports `bind:activeTab`.
|
|
2254
|
+
- **`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.
|
|
2255
|
+
- **`Dropdown`**: new optional `header` snippet; Home/End/Space behave normally inside text inputs in the menu.
|
|
2256
|
+
- **`Toggle`**: new `id`, `name` and `value` props for label pairing and form submission.
|
|
2257
|
+
- **`ThemeToggle`**: a consumer `onclick` no longer overrides the toggle; pre-mount placeholder respects `size`/`variant` (no layout shift).
|
|
2258
|
+
|
|
2259
|
+
### Overlays
|
|
2260
|
+
|
|
2261
|
+
- **`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.
|
|
2262
|
+
- **`Toaster`**: countdown is no longer announced every second; it pauses on hover/focus; no double announcements from nested live regions.
|
|
2263
|
+
- **`NavigationMenu`**: fixed a leaked document `mousedown` listener; panels use the disclosure pattern (no `role="menu"` / `aria-haspopup`); new `ariaLabel` prop.
|
|
2264
|
+
- **`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.
|
|
2265
|
+
- **`Alert`**: `closable` now hides the alert; new bindable `open` prop.
|
|
2266
|
+
- **`Tooltip`**: Escape no longer steals focus; CSS custom properties no longer leak onto `:root`.
|
|
2267
|
+
- **`util/ssr-safe`**: timer helpers typed with `ReturnType<typeof setTimeout/setInterval>` instead of `NodeJS.Timeout`, so consumers don't need `@types/node`.
|
|
2268
|
+
|
|
2269
|
+
### Surfaces
|
|
2270
|
+
|
|
2271
|
+
- **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`.
|
|
2272
|
+
- **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.
|
|
2273
|
+
- **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.
|
|
2274
|
+
- **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`.
|
|
2275
|
+
- **Dark `--color-description`** moves one step lighter (`base-650`) so description text keeps ≥ 4.5:1 contrast on every surface level, including overlays.
|
|
2276
|
+
- **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.
|
|
2277
|
+
|
|
2278
|
+
### Theme fixes
|
|
2279
|
+
|
|
2280
|
+
- **`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.
|
|
2281
|
+
- **`--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).
|
|
2282
|
+
- **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.
|
|
2283
|
+
- **`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.
|
|
2284
|
+
|
|
2285
|
+
### Packaging
|
|
2286
|
+
|
|
2287
|
+
- **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.
|
|
2288
|
+
- **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.
|
|
2289
|
+
- **`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.
|
|
2290
|
+
- **`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.
|
|
2291
|
+
- **`CodeBlock`**: window-chrome dots use semantic tokens (`bg-error` / `bg-warning` / `bg-success`) instead of raw Tailwind palette classes; props are now typed.
|
|
2292
|
+
- **`check:tokens`** also flags raw Tailwind palette utilities (e.g. `bg-red-500`) in `src/components`.
|
|
2293
|
+
- **Repo**: `dist/`, `.svelte-kit/` and `storybook-static/` are no longer tracked in git; unused Storybook template assets and `src/_tmp-lucide-import.ts` removed.
|
|
2294
|
+
- **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.
|
|
2295
|
+
|
|
2296
|
+
## [7.0.2] - 2026-04-20
|
|
2297
|
+
|
|
2298
|
+
### Changed
|
|
2299
|
+
|
|
2300
|
+
- **`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.
|
|
2301
|
+
- **`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`**.
|
|
2302
|
+
- **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.
|
|
2303
|
+
|
|
2304
|
+
## [7.0.1] - 2026-04-19
|
|
2305
|
+
|
|
2306
|
+
### Fixed
|
|
2307
|
+
|
|
2308
|
+
- **`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.
|
|
2309
|
+
- **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.
|
|
2310
|
+
- **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`.
|
|
2311
|
+
|
|
2312
|
+
### Added
|
|
2313
|
+
|
|
2314
|
+
- **`docs/lucide-icons.md`**: Lists Lucide icons imported by published components for teams using a custom `@lucide/svelte` barrel.
|
|
2315
|
+
|
|
2316
|
+
### Changed
|
|
2317
|
+
|
|
2318
|
+
- **`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).
|
|
2319
|
+
|
|
2320
|
+
### Migration / discovery
|
|
2321
|
+
|
|
2322
|
+
- **`Navbar` → `TopNavbar`**: The older **`Navbar`** export was removed in **6.0.0**; use **`TopNavbar`**. See the **6.0.0** migration notes in this file.
|
|
2323
|
+
|
|
2324
|
+
## [7.0.0] - 2026-04-19
|
|
2325
|
+
|
|
2326
|
+
### Breaking changes
|
|
2327
|
+
|
|
2328
|
+
- **`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).
|
|
2329
|
+
- **`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.
|
|
2330
|
+
|
|
2331
|
+
### Added
|
|
2332
|
+
|
|
2333
|
+
- **`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.
|
|
2334
|
+
- **`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"`).
|
|
2335
|
+
|
|
2336
|
+
### Changed
|
|
2337
|
+
|
|
2338
|
+
- **`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.
|
|
2339
|
+
- **`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.
|
|
2340
|
+
- **`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.
|
|
2341
|
+
- **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).
|
|
2342
|
+
- **`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`**).
|
|
2343
|
+
|
|
2344
|
+
### Fixed
|
|
2345
|
+
|
|
2346
|
+
- **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.
|
|
2347
|
+
|
|
2348
|
+
### Migration (from 6.x)
|
|
2349
|
+
|
|
2350
|
+
1. **`Page` / `EmptyState`**: If layout relied on the old max widths, add them explicitly:
|
|
2351
|
+
- `<Page className="max-w-4xl">` or wrap content in **`Container`** / **`Section`**.
|
|
2352
|
+
- `<EmptyState className="max-w-lg" …>` (or another **`max-w-*`** from your layout policy).
|
|
2353
|
+
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).
|
|
2354
|
+
3. **Demos copying Lucide icon names**: Replace **`CircleHelp`** with **`CircleQuestionMark`** when upgrading Lucide past names that removed **`CircleHelp`**.
|
|
2355
|
+
|
|
2356
|
+
## [6.0.0] - 2026-04-12
|
|
2357
|
+
|
|
2358
|
+
### Breaking changes
|
|
2359
|
+
|
|
2360
|
+
- Removed the `Navigation` organism; use `TopNavbar` with `items`, optional `embedded`, or a `nav` snippet instead.
|
|
2361
|
+
- Renamed `SidebarProjectPanel` to `SidebarPanel` (including the `SidebarPanelItem` type).
|
|
2362
|
+
|
|
2363
|
+
### Added
|
|
2364
|
+
|
|
2365
|
+
- Package subpath exports `zabi-components/colors` and `zabi-components/css` for theme and full bundle CSS.
|
|
2366
|
+
- Published theme documentation files in the package (`THEME.md`, `THEMING.md`, `docs/theme-imports.md`).
|
|
2367
|
+
|
|
2368
|
+
### Changed
|
|
2369
|
+
|
|
2370
|
+
- Theme build runs `sync:tokens` before CSS generation; added theme output tests (`test:themes`).
|
|
2371
|
+
- Refined default border styling on several components.
|
|
2372
|
+
|
|
2373
|
+
### Migration
|
|
2374
|
+
|
|
2375
|
+
- Replace `import { Navigation } from 'zabi-components'` (or organisms path) with `TopNavbar` and the documented props/snippets.
|
|
2376
|
+
- Replace `SidebarProjectPanel` imports with `SidebarPanel` and align prop/type names to `SidebarPanelItem` where applicable.
|
|
2377
|
+
|
|
2378
|
+
## Pending deprecations
|
|
2379
|
+
|
|
2380
|
+
- 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`.
|
|
2381
|
+
|
|
2382
|
+
## [5.0.22] - 2026-03-16
|
|
2383
|
+
|
|
2384
|
+
### ✨ **Features**
|
|
2385
|
+
|
|
2386
|
+
- **List Components**: Added new `List` and `ListItem` components with documentation and usage examples
|
|
2387
|
+
- **Sidebar**: Added `SidebarNavigation` and `SidebarPanel` with improved navigation behaviors
|
|
2388
|
+
|
|
2389
|
+
### 🔧 **Improvements**
|
|
2390
|
+
|
|
2391
|
+
- **Card Components**: Refactored `Card`, `CardHeader`, and `CardContent` and simplified composition patterns
|
|
2392
|
+
- **Documentation**: Updated Storybook setup and improved component documentation quality
|
|
2393
|
+
- **Config & Environment**: Refined environment variable definitions and release metadata handling
|
|
2394
|
+
|
|
2395
|
+
### ✅ **No Breaking Changes**
|
|
2396
|
+
|
|
2397
|
+
This is a backward-compatible update.
|
|
2398
|
+
|
|
2399
|
+
---
|
|
2400
|
+
|
|
2401
|
+
## [5.0.21] - 2026-01-17
|
|
2402
|
+
|
|
2403
|
+
### ✨ **Features**
|
|
2404
|
+
|
|
2405
|
+
- **IconButton**: Added a new icon-only button with variants, sizes, and accessible labels
|
|
2406
|
+
- **Exports & Storybook**: Included IconButton in atom exports and Storybook registry
|
|
2407
|
+
- **Docs**: Added IconButton examples to the docs and components showcase
|
|
2408
|
+
|
|
2409
|
+
### 🔧 **Improvements**
|
|
2410
|
+
|
|
2411
|
+
- **Navigation**: Header navigation now stacks for mobile-friendly layouts
|
|
2412
|
+
- **Select**: Improved selected label truncation and helper message layout
|
|
2413
|
+
- **Button**: Standardized disabled behavior without pointer-events override
|
|
2414
|
+
|
|
2415
|
+
### ✅ **No Breaking Changes**
|
|
2416
|
+
|
|
2417
|
+
This is a backward-compatible update.
|
|
2418
|
+
|
|
2419
|
+
---
|
|
2420
|
+
|
|
2421
|
+
## [5.0.20] - 2025-01-27
|
|
2422
|
+
|
|
2423
|
+
### 🔧 **Patch Release**
|
|
2424
|
+
|
|
2425
|
+
- **Maintenance**: General improvements and stability updates
|
|
2426
|
+
- **Build**: Enhanced build process and type definitions
|
|
2427
|
+
- **Documentation**: Minor documentation updates
|
|
2428
|
+
|
|
2429
|
+
### ✅ **No Breaking Changes**
|
|
2430
|
+
|
|
2431
|
+
This is a backward-compatible patch release.
|
|
2432
|
+
|
|
2433
|
+
---
|
|
2434
|
+
|
|
2435
|
+
## [5.0.19] - 2025-01-27
|
|
2436
|
+
|
|
2437
|
+
### 🐛 **Bug Fixes**
|
|
2438
|
+
|
|
2439
|
+
#### **Modal Component**
|
|
2440
|
+
- **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
|
|
2441
|
+
- **Internal Focus Utilities**: Moved focus management utilities (`focus-utils.ts`) to be properly included in the build output
|
|
2442
|
+
- **Build Process**: Added post-build script to fix import paths in built components
|
|
2443
|
+
- This fixes the "Failed to load url ../../routes/lib/focus-utils.js" error that occurred during SSR
|
|
2444
|
+
|
|
2445
|
+
## [5.0.18] - 2025-01-27
|
|
2446
|
+
|
|
2447
|
+
### ✨ **Component Enhancements & Documentation**
|
|
2448
|
+
|
|
2449
|
+
#### **Card Component Improvements**
|
|
2450
|
+
- **Compound Components**: Added CardHeader, CardContent, CardFooter, CardTitle, and CardDescription sub-components
|
|
2451
|
+
- Enables flexible card composition with better structure
|
|
2452
|
+
- Maintains backward compatibility with existing title/description/image props
|
|
2453
|
+
- Supports both old and new API patterns
|
|
2454
|
+
- **Enhanced Variants**: Improved elevated, outlined, and flat variants with better hover states
|
|
2455
|
+
- **Accessibility**: Added keyboard navigation (Enter/Space) and proper ARIA attributes for interactive cards
|
|
2456
|
+
- **Size Variants**: Enhanced size support (sm, md, lg) with responsive padding and typography
|
|
2457
|
+
|
|
2458
|
+
#### **Button Component Enhancements**
|
|
2459
|
+
- **New Variants**: Added ghost, outline, and link variants for more design flexibility
|
|
2460
|
+
- **Improved Focus States**: Enhanced focus ring styles for better accessibility
|
|
2461
|
+
- **Size Refinements**: Improved size variants with better typography and spacing
|
|
2462
|
+
- **Better Disabled States**: Enhanced disabled state styling and interaction
|
|
2463
|
+
|
|
2464
|
+
#### **Badge Component Updates**
|
|
2465
|
+
- **Icon Support**: Added optional icon display for success, warning, error, and info variants
|
|
2466
|
+
- **Size Variants**: Enhanced size support with responsive icon sizing
|
|
2467
|
+
- **Visual Consistency**: Improved color system integration
|
|
2468
|
+
|
|
2469
|
+
#### **Component Accessibility Improvements**
|
|
2470
|
+
- **Modal Component**: Enhanced focus management and keyboard navigation
|
|
2471
|
+
- **Dropdown Component**: Improved ARIA attributes and keyboard navigation
|
|
2472
|
+
- **Card Component**: Added proper roles and keyboard handlers for interactive cards
|
|
2473
|
+
- **Focus Utilities**: New focus-utils.ts for better focus management across components
|
|
2474
|
+
|
|
2475
|
+
#### **Documentation Additions**
|
|
2476
|
+
- **ACCESSIBILITY.md**: Comprehensive accessibility documentation with component-by-component analysis
|
|
2477
|
+
- **KEYBOARD_NAVIGATION.md**: Detailed keyboard navigation patterns and best practices
|
|
2478
|
+
- **VARIANTS.md**: Complete variant system documentation
|
|
2479
|
+
- **THEMING.md**: Enhanced theming guide with customization examples
|
|
2480
|
+
- **THEME_QUICK_REFERENCE.md**: Quick reference for theme customization
|
|
2481
|
+
- **SHADCN_INSPIRATION.md**: Design system inspiration and improvement roadmap
|
|
2482
|
+
|
|
2483
|
+
#### **Theme & Styling Improvements**
|
|
2484
|
+
- **Color System**: Enhanced color variable resolution and consistency
|
|
2485
|
+
- **Theme Files**: Updated theme CSS files with improved structure
|
|
2486
|
+
- **Build Process**: Enhanced CSS build scripts for better theme processing
|
|
2487
|
+
|
|
2488
|
+
#### **Build & Development**
|
|
2489
|
+
- **Type Definitions**: Updated TypeScript definitions for new components
|
|
2490
|
+
- **Component Exports**: Added exports for new Card compound components
|
|
2491
|
+
- **Storybook**: Enhanced Card stories with compound component examples
|
|
2492
|
+
|
|
2493
|
+
### ✅ **No Breaking Changes**
|
|
2494
|
+
|
|
2495
|
+
This is a backward-compatible update. Existing component usage continues to work, with new features available as optional enhancements.
|
|
2496
|
+
|
|
2497
|
+
---
|
|
2498
|
+
|
|
2499
|
+
## [5.0.17] - 2025-01-27
|
|
2500
|
+
|
|
2501
|
+
### 🔧 **Patch Release**
|
|
2502
|
+
|
|
2503
|
+
- **Documentation**: Updated changelog with version 5.0.16 release notes
|
|
2504
|
+
- **Build Improvements**: Adjusted theme validation script to be less strict for dark theme structure differences
|
|
2505
|
+
- **Version Bump**: Patch release for improved stability
|
|
2506
|
+
|
|
2507
|
+
---
|
|
2508
|
+
|
|
2509
|
+
## [5.0.16] - 2025-01-27
|
|
2510
|
+
|
|
2511
|
+
### 🎨 **Color System & Component Styling Updates**
|
|
2512
|
+
|
|
2513
|
+
#### **Color System Refactoring**
|
|
2514
|
+
- **Enhanced Color Variables**: Improved color system with better CSS variable resolution
|
|
2515
|
+
- **Theme Updates**: Updated theme files with improved color consistency
|
|
2516
|
+
- **Build Process**: Enhanced CSS build scripts for better color processing
|
|
2517
|
+
|
|
2518
|
+
#### **Component Style Improvements**
|
|
2519
|
+
- **Checkbox Component**: Updated styling to match M3 design system with improved visual consistency
|
|
2520
|
+
- **Input Component**: Enhanced color system integration and styling updates
|
|
2521
|
+
- **Multiple Components**: Updated Button, CodeBlock, ColorPicker, FeatureCard, Progress, Skeleton, Textarea, Toast, Toggle, and more with consistent styling
|
|
2522
|
+
- **Molecule Components**: Updated Alert, ComponentDemo, Dropdown, ImageUpload, Modal, SlideUp, and Tabs with improved styling
|
|
2523
|
+
|
|
2524
|
+
#### **Storybook Configuration**
|
|
2525
|
+
- **Story Organization**: Reorganized stories into atoms/, molecules/, and organisms/ folders for better structure
|
|
2526
|
+
- **Enhanced Stories**: Updated component stories with improved examples and controls
|
|
2527
|
+
- **Configuration Updates**: Enhanced Storybook configuration for better development experience
|
|
2528
|
+
|
|
2529
|
+
#### **Build & Dependency Updates**
|
|
2530
|
+
- **Dependency Updates**: Updated package dependencies for improved compatibility
|
|
2531
|
+
- **Build Scripts**: Enhanced CSS build and cleanup scripts
|
|
2532
|
+
- **Type Definitions**: Updated TypeScript definitions for better type safety
|
|
2533
|
+
|
|
2534
|
+
### ✅ **No Breaking Changes**
|
|
2535
|
+
|
|
2536
|
+
This is a backward-compatible update focusing on styling improvements and build enhancements.
|
|
2537
|
+
|
|
2538
|
+
---
|
|
2539
|
+
|
|
2540
|
+
## [5.0.15] - 2025-01-27
|
|
2541
|
+
|
|
2542
|
+
### 🎨 **Color System Improvements**
|
|
2543
|
+
|
|
2544
|
+
#### **Standalone Colors File**
|
|
2545
|
+
- **New Export**: Added `zabi-components-colors.css` as a separate export for consumer apps
|
|
2546
|
+
- Contains resolved CSS custom properties that work without Tailwind CSS processing
|
|
2547
|
+
- Perfect for apps not using Tailwind CSS v4
|
|
2548
|
+
- Includes all color variables with resolved hex values
|
|
2549
|
+
- Supports both light and dark mode variants
|
|
2550
|
+
- **Package Export**: Added `./dist/zabi-components-colors.css` to package.json exports
|
|
2551
|
+
- **Usage**: Consumer apps can now import colors independently:
|
|
2552
|
+
```css
|
|
2553
|
+
/* For Tailwind v4 users */
|
|
2554
|
+
@import "zabi-components/dist/zabi-components.css";
|
|
2555
|
+
|
|
2556
|
+
/* For non-Tailwind users */
|
|
2557
|
+
@import "zabi-components/dist/zabi-components-colors.css";
|
|
2558
|
+
```
|
|
2559
|
+
|
|
2560
|
+
#### **Component Cleanup**
|
|
2561
|
+
- **Badge Component**: Removed all arbitrary Tailwind values (`[var(--color-*)]`)
|
|
2562
|
+
- Now uses proper utility classes: `bg-success border-success` instead of `bg-success border-[var(--color-success)]`
|
|
2563
|
+
- More maintainable and consistent code
|
|
2564
|
+
- **Added Background Utilities**: Created explicit background utility classes to match border utilities
|
|
2565
|
+
- Added: `bg-success`, `bg-warning`, `bg-error`, `bg-info`, `bg-neutral`, `bg-energetic`, `bg-secondary`
|
|
2566
|
+
- Ensures consistent styling across all components
|
|
2567
|
+
|
|
2568
|
+
#### **Build Improvements**
|
|
2569
|
+
- **Simplified Build Script**: Cleaned up CSS build process
|
|
2570
|
+
- **Better Separation**: Colors file is now separate from main CSS, making it easier to import independently
|
|
2571
|
+
|
|
2572
|
+
### 🎯 **Technical Details**
|
|
2573
|
+
|
|
2574
|
+
- **Color System**: Colors now work correctly in consumer apps regardless of Tailwind CSS setup
|
|
2575
|
+
- **Code Quality**: Removed all arbitrary value syntax for better maintainability
|
|
2576
|
+
- **Consistency**: All color utilities now follow the same pattern
|
|
2577
|
+
|
|
2578
|
+
### ✅ **No Breaking Changes**
|
|
2579
|
+
|
|
2580
|
+
This is a backward-compatible update. Existing usage continues to work, with improved color system support.
|
|
2581
|
+
|
|
2582
|
+
---
|
|
2583
|
+
|
|
2584
|
+
## [5.0.14] - 2025-01-27
|
|
2585
|
+
|
|
2586
|
+
### ✨ **Component Improvements**
|
|
2587
|
+
|
|
2588
|
+
#### **Textarea Component**
|
|
2589
|
+
- **Visual Consistency**: Updated Textarea styling to match Input component exactly
|
|
2590
|
+
- Changed background to `bg-brand-100` (matching Input)
|
|
2591
|
+
- Updated border radius from `rounded-md` to `rounded-lg`
|
|
2592
|
+
- Removed border for default variant (matches Input's borderless design)
|
|
2593
|
+
- Updated focus states to use `focus:ring-2 focus:ring-brand-500` with `focus:ring-offset-0`
|
|
2594
|
+
- Standardized placeholder and text colors to match Input
|
|
2595
|
+
- **Simplified API**: Removed `size` prop - Textarea now uses a fixed size (matching Input's default "md" size)
|
|
2596
|
+
- **Enhanced Features**: Added message support with icons for success, warning, and error variants (matching Input component)
|
|
2597
|
+
|
|
2598
|
+
#### **Storybook Updates**
|
|
2599
|
+
- **Storybook 10 Compatibility**: Standardized `argTypes` controls across all stories
|
|
2600
|
+
- Updated Alert, Badge, Toggle, Tooltip, and SlideUp stories to use string shorthand for controls
|
|
2601
|
+
- Improved consistency and cleaner code
|
|
2602
|
+
- **Textarea Stories**: Added message examples for success, warning, and error variants
|
|
2603
|
+
- **Removed Deprecated Stories**: Removed Small and Large Textarea stories (size prop removed)
|
|
2604
|
+
|
|
2605
|
+
### 🎯 **Technical Details**
|
|
2606
|
+
|
|
2607
|
+
- **Styling Alignment**: Textarea now visually matches Input component for consistent form design
|
|
2608
|
+
- **API Simplification**: Removed unnecessary size variations from Textarea
|
|
2609
|
+
- **Storybook Modernization**: All stories now use Storybook 10 best practices
|
|
2610
|
+
|
|
2611
|
+
### ✅ **No Breaking Changes**
|
|
2612
|
+
|
|
2613
|
+
This is a backward-compatible update. Existing Textarea usage continues to work, with improved visual consistency.
|
|
2614
|
+
|
|
2615
|
+
---
|
|
2616
|
+
|
|
2617
|
+
## [4.0.1] - 2025-01-27
|
|
2618
|
+
|
|
2619
|
+
### 🛡️ **SSR Safety Overhaul - "Zero Runtime Errors" Edition**
|
|
2620
|
+
|
|
2621
|
+
#### ✅ **Critical SSR Issues Resolved**
|
|
2622
|
+
|
|
2623
|
+
This release completely eliminates the "Cannot read properties of null (reading 'f')" error and ensures 100% SSR safety across all components.
|
|
2624
|
+
|
|
2625
|
+
#### 🔧 **SSR Safety Improvements**
|
|
2626
|
+
|
|
2627
|
+
##### **Route Navigation Fixed**
|
|
2628
|
+
- **Replaced `window.location.href`**: All route pages now use SvelteKit's `goto()` from `$app/navigation`
|
|
2629
|
+
- **SSR-Safe Navigation**: No more direct window access during server-side rendering
|
|
2630
|
+
- **Files Updated**: `src/routes/+page.svelte`, `src/routes/docs/+page.svelte`
|
|
2631
|
+
|
|
2632
|
+
##### **Component Lifecycle Fixed**
|
|
2633
|
+
- **ThemeToggle Component**: Replaced `$effect` with `onMount` for initial setup
|
|
2634
|
+
- **SSRSafe Component**: Simplified checks to rely solely on `mounted` state
|
|
2635
|
+
- **Eliminated Race Conditions**: Fixed timing issues during hydration
|
|
2636
|
+
|
|
2637
|
+
##### **ID Generation Fixed**
|
|
2638
|
+
- **Input Component**: Moved ID generation to `onMount` to prevent hydration mismatches
|
|
2639
|
+
- **Checkbox Component**: Moved ID generation to `onMount` for consistency
|
|
2640
|
+
- **SSR-Safe IDs**: No more SSR/client ID mismatches
|
|
2641
|
+
|
|
2642
|
+
##### **Enhanced SSR Utilities**
|
|
2643
|
+
- **New `ssr-safe.ts`**: Comprehensive SSR-safe utility functions
|
|
2644
|
+
- **SvelteKit Integration**: Uses `browser` from `$app/environment`
|
|
2645
|
+
- **Safe API Access**: `safeLocalStorage()`, `safeDocument()`, `safeWindow()`
|
|
2646
|
+
- **ID Generation**: `generateId()` function for consistent SSR-safe IDs
|
|
2647
|
+
|
|
2648
|
+
#### 🎯 **Technical Improvements**
|
|
2649
|
+
|
|
2650
|
+
##### **Consistent SSR Pattern**
|
|
2651
|
+
```svelte
|
|
2652
|
+
import { safeLocalStorage, safeDocument } from "../../lib/ssr-safe";
|
|
2653
|
+
|
|
2654
|
+
onMount(() => {
|
|
2655
|
+
mounted = true;
|
|
2656
|
+
const storage = safeLocalStorage();
|
|
2657
|
+
if (storage) {
|
|
2658
|
+
// Safe browser API access
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
```
|
|
2662
|
+
|
|
2663
|
+
##### **SvelteKit Best Practices**
|
|
2664
|
+
- Uses `browser` from `$app/environment` instead of manual checks
|
|
2665
|
+
- Uses `goto()` from `$app/navigation` for routing
|
|
2666
|
+
- Follows SvelteKit SSR patterns throughout
|
|
2667
|
+
|
|
2668
|
+
##### **Hydration Safety**
|
|
2669
|
+
- No more SSR/client mismatches
|
|
2670
|
+
- Consistent ID generation across server and client
|
|
2671
|
+
- Proper lifecycle management
|
|
2672
|
+
|
|
2673
|
+
#### ✅ **Results Achieved**
|
|
2674
|
+
|
|
2675
|
+
##### **Build Status**
|
|
2676
|
+
- ✅ **Successful Builds**: All builds complete without errors
|
|
2677
|
+
- ✅ **Type Safety**: 0 TypeScript errors in zabi-components code
|
|
2678
|
+
- ✅ **SSR Compatible**: Full server-side rendering support
|
|
2679
|
+
|
|
2680
|
+
##### **Runtime Safety**
|
|
2681
|
+
- ✅ **Zero Runtime Errors**: Eliminated all "Cannot read properties of null" errors
|
|
2682
|
+
- ✅ **Hydration Safe**: No more client/server mismatches
|
|
2683
|
+
- ✅ **Theme Toggle**: Works correctly on first load
|
|
2684
|
+
- ✅ **Form Inputs**: Stable IDs across SSR/hydration
|
|
2685
|
+
|
|
2686
|
+
##### **Cross-Environment Compatibility**
|
|
2687
|
+
- ✅ **SvelteKit SSR**: Full compatibility with server-side rendering
|
|
2688
|
+
- ✅ **Client Hydration**: Smooth hydration without errors
|
|
2689
|
+
- ✅ **Production Ready**: Works correctly in production environments
|
|
2690
|
+
|
|
2691
|
+
#### 📋 **Files Modified**
|
|
2692
|
+
|
|
2693
|
+
1. `src/routes/+page.svelte` - Navigation fixes (6 instances)
|
|
2694
|
+
2. `src/routes/docs/+page.svelte` - Navigation fixes (2 instances)
|
|
2695
|
+
3. `src/components/atoms/ThemeToggle.svelte` - SSR safety improvements
|
|
2696
|
+
4. `src/components/atoms/Input.svelte` - ID generation fixes
|
|
2697
|
+
5. `src/components/atoms/Checkbox.svelte` - ID generation fixes
|
|
2698
|
+
6. `src/components/SSRSafe.svelte` - Simplified checks
|
|
2699
|
+
7. `src/lib/ssr-safe.ts` - Enhanced SSR utilities
|
|
2700
|
+
|
|
2701
|
+
#### 🚀 **Performance Benefits**
|
|
2702
|
+
|
|
2703
|
+
- **Faster Builds**: Reduced build time and complexity
|
|
2704
|
+
- **Better Reliability**: No more runtime crashes
|
|
2705
|
+
- **Improved DX**: Better developer experience with consistent patterns
|
|
2706
|
+
- **Production Ready**: Fully tested in production environments
|
|
2707
|
+
|
|
2708
|
+
#### 🔄 **Migration Notes**
|
|
2709
|
+
|
|
2710
|
+
**No Breaking Changes**: This is a backward-compatible update that fixes SSR issues without changing component APIs.
|
|
2711
|
+
|
|
2712
|
+
**For Developers**: All existing component usage remains the same - the fixes are internal and transparent to users.
|
|
2713
|
+
|
|
2714
|
+
---
|
|
2715
|
+
|
|
2716
|
+
## [2.1.0] - 2025-01-27
|
|
2717
|
+
|
|
2718
|
+
### 🚀 Major Event Handling Refactor - "Cross-Framework Compatible" Edition
|
|
2719
|
+
|
|
2720
|
+
#### ⚠️ **BREAKING CHANGES** - Event Handling Overhaul
|
|
2721
|
+
|
|
2722
|
+
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.
|
|
2723
|
+
|
|
2724
|
+
#### 🔧 **Event System Refactoring**
|
|
2725
|
+
- **Removed `createEventDispatcher`**: Eliminated all `createEventDispatcher` usage across 18 components
|
|
2726
|
+
- **Implemented Event Forwarding**: All components now use `{...$$restProps}` for native DOM event forwarding
|
|
2727
|
+
- **SSR-Safe**: Fixed hydration mismatches and production build errors
|
|
2728
|
+
- **Cross-Framework Compatible**: Components now work in React, Vue, Svelte, and vanilla JS
|
|
2729
|
+
|
|
2730
|
+
#### 🎯 **Components Refactored**
|
|
2731
|
+
|
|
2732
|
+
##### **Form Components**
|
|
2733
|
+
- **Input**: Replaced `bind:value` with `value` prop + `on:input` handler
|
|
2734
|
+
- **Textarea**: Replaced `bind:value` with `value` prop + `on:input` handler
|
|
2735
|
+
- **Select**: Replaced `bind:value` with `value` prop + `on:change` handler
|
|
2736
|
+
- **Checkbox**: Replaced `bind:checked` with `checked` prop + `on:change` handler
|
|
2737
|
+
|
|
2738
|
+
##### **Interactive Components**
|
|
2739
|
+
- **Button**: Added `{...$$restProps}` for native event forwarding
|
|
2740
|
+
- **Toggle**: Added event handler + `{...$$restProps}` for cross-framework compatibility
|
|
2741
|
+
- **ThemeToggle**: Added `{...$$restProps}` for event forwarding
|
|
2742
|
+
|
|
2743
|
+
##### **Complex Components**
|
|
2744
|
+
- **Modal**: Removed `createEventDispatcher`, simplified event handling
|
|
2745
|
+
- **Tabs**: Removed `createEventDispatcher`, uses direct state management
|
|
2746
|
+
- **ImageUpload**: Removed `createEventDispatcher`, simplified file handling
|
|
2747
|
+
- **ContactForm**: Updated to use new event patterns
|
|
2748
|
+
- **Form**: Removed `createEventDispatcher`, added `{...$$restProps}`
|
|
2749
|
+
- **Alert**: Removed `createEventDispatcher`, added `{...$$restProps}`
|
|
2750
|
+
- **Toast**: Removed `createEventDispatcher`, simplified close handling
|
|
2751
|
+
- **Badge**: Removed `createEventDispatcher`, added `{...$$restProps}`
|
|
2752
|
+
- **SlideUp**: Removed `createEventDispatcher`, simplified modal handling
|
|
2753
|
+
- **TopNavbar**: Removed `createEventDispatcher`, added `{...$$restProps}`
|
|
2754
|
+
- **Navigation**: Removed `createEventDispatcher`, simplified navigation
|
|
2755
|
+
|
|
2756
|
+
#### ✅ **Benefits Achieved**
|
|
2757
|
+
|
|
2758
|
+
##### **SSR/Production Safe**
|
|
2759
|
+
- ✅ **No more hydration errors** in SSR environments
|
|
2760
|
+
- ✅ **Production builds work correctly** without runtime errors
|
|
2761
|
+
- ✅ **Eliminated `createEventDispatcher` errors** in SvelteKit applications
|
|
2762
|
+
|
|
2763
|
+
##### **Cross-Framework Compatible**
|
|
2764
|
+
- ✅ **React Applications**: Components work with standard React event handlers
|
|
2765
|
+
- ✅ **Vue Applications**: Compatible with Vue's event system
|
|
2766
|
+
- ✅ **Vanilla JavaScript**: Full compatibility with native DOM events
|
|
2767
|
+
- ✅ **Svelte/SvelteKit**: Maintains full compatibility with improved reliability
|
|
2768
|
+
|
|
2769
|
+
##### **Standards Compliant**
|
|
2770
|
+
- ✅ **Native DOM Events**: Uses web standards instead of framework-specific events
|
|
2771
|
+
- ✅ **Better Performance**: Reduced JavaScript overhead and bundle size
|
|
2772
|
+
- ✅ **More Predictable**: Event handling follows web standards
|
|
2773
|
+
|
|
2774
|
+
#### 🔄 **Migration Required**
|
|
2775
|
+
|
|
2776
|
+
**Before (Old Pattern):**
|
|
2777
|
+
```svelte
|
|
2778
|
+
<!-- This would cause SSR errors -->
|
|
2779
|
+
<Button on:click={handleClick}>Click me</Button>
|
|
2780
|
+
<Input bind:value={inputValue} />
|
|
2781
|
+
<Modal bind:isOpen onclick={handleClose} />
|
|
2782
|
+
```
|
|
2783
|
+
|
|
2784
|
+
**After (New Pattern):**
|
|
2785
|
+
```svelte
|
|
2786
|
+
<!-- Now works in all environments -->
|
|
2787
|
+
<Button on:click={handleClick}>Click me</Button>
|
|
2788
|
+
<Input value={inputValue} on:input={(e) => inputValue = e.target.value} />
|
|
2789
|
+
<Modal bind:isOpen on:click={handleClose} />
|
|
2790
|
+
```
|
|
2791
|
+
|
|
2792
|
+
#### 📋 **Breaking Changes Summary**
|
|
2793
|
+
|
|
2794
|
+
1. **Form Components**: Must use `value` prop + event handlers instead of `bind:value`
|
|
2795
|
+
2. **Event Names**: Some custom events replaced with native DOM events
|
|
2796
|
+
3. **Event Structure**: Simplified event structures across all components
|
|
2797
|
+
4. **SSR Compatibility**: Components now work correctly in SSR environments
|
|
2798
|
+
|
|
2799
|
+
#### 🛠️ **Technical Details**
|
|
2800
|
+
|
|
2801
|
+
- **Event Forwarding**: All interactive elements now use `{...$$restProps}`
|
|
2802
|
+
- **Native Events**: Replaced custom events with standard DOM events where possible
|
|
2803
|
+
- **State Management**: Simplified internal state management
|
|
2804
|
+
- **Type Safety**: Maintained full TypeScript support with updated types
|
|
2805
|
+
|
|
2806
|
+
#### 🎯 **Migration Guide**
|
|
2807
|
+
|
|
2808
|
+
See the updated README.md for detailed migration examples and new usage patterns.
|
|
2809
|
+
|
|
2810
|
+
---
|
|
2811
|
+
|
|
2812
|
+
## [2.0.2] - 2025-01-27
|
|
2813
|
+
|
|
2814
|
+
### 🎯 Major Simplification - "Less is More" Edition
|
|
2815
|
+
|
|
2816
|
+
#### ✨ Simplified Components
|
|
2817
|
+
- **Massive Code Reduction**: Reduced component complexity by 60-80% across the board
|
|
2818
|
+
- **Modern CSS-First Approach**: Replaced complex JavaScript with modern CSS-only solutions
|
|
2819
|
+
- **Unified Form Component**: Consolidated 4 form components into 1 simple Form component
|
|
2820
|
+
- **Removed Complex Managers**: Eliminated NotificationManager, ToastManager, and other complex utilities
|
|
2821
|
+
|
|
2822
|
+
#### 🔧 Atom Components Simplified
|
|
2823
|
+
- **Badge**: Removed complex variants, icons, and animations - now just basic color variants
|
|
2824
|
+
- **Button**: Simplified to 3 variants (primary, secondary, danger) with minimal props
|
|
2825
|
+
- **Card**: Merged ModernCard into Card, removed complex styling and animations
|
|
2826
|
+
- **Checkbox**: Removed complex validation, sizing, and custom CSS
|
|
2827
|
+
- **Input**: Simplified to essential props only, removed complex validation system
|
|
2828
|
+
- **Heading**: Clean heading component with just level and text props
|
|
2829
|
+
- **Progress**: Basic progress bar with simple percentage display
|
|
2830
|
+
- **Select**: Simplified dropdown with basic options support
|
|
2831
|
+
- **Textarea**: Essential textarea with minimal configuration
|
|
2832
|
+
- **Toggle**: Fixed-size toggle with simple on/off state
|
|
2833
|
+
- **ThemeToggle**: Emoji-based theme switcher, removed complex styling
|
|
2834
|
+
- **Toast**: Fixed-position toast with basic message display
|
|
2835
|
+
- **Tooltip**: CSS-only positioning with data-placement attributes
|
|
2836
|
+
- **Skeleton**: Simple loading placeholder with basic styling
|
|
2837
|
+
- **TextAlignment**: Emoji-based alignment selector
|
|
2838
|
+
- **OptimizedImage**: Basic image component with lazy loading
|
|
2839
|
+
- **ColorPicker**: Simple color grid without complex dropdown logic
|
|
2840
|
+
|
|
2841
|
+
#### 🧩 Molecule Components Simplified
|
|
2842
|
+
- **Dropdown**: CSS-only positioning, removed complex JavaScript calculations
|
|
2843
|
+
- **ImageUpload**: Direct file selection, removed modal and progress bars
|
|
2844
|
+
- **Form**: Unified form component, removed complex validation and field management
|
|
2845
|
+
- **Modal**: Simple modal with basic backdrop and close functionality
|
|
2846
|
+
- **SlideUp**: CSS-only slide-up panel, removed complex state management
|
|
2847
|
+
- **Tabs**: Basic tab navigation with keyboard support, removed complex utilities
|
|
2848
|
+
|
|
2849
|
+
#### 🏗️ Organism Components Simplified
|
|
2850
|
+
- **TopNavbar**: Responsive top navbar with mobile menu, removed complex variants
|
|
2851
|
+
- **Navigation**: Simple navigation list with active state
|
|
2852
|
+
- **Removed Complex Components**: Deleted NavigationItem, NotificationManager, ToastManager
|
|
2853
|
+
|
|
2854
|
+
#### 🗑️ Removed Complexity
|
|
2855
|
+
- **External Utility Functions**: Removed complex tabs-utils, navigation-utils
|
|
2856
|
+
- **Complex Type Definitions**: Simplified event types and interfaces
|
|
2857
|
+
- **Svelte Transitions**: Replaced with CSS-only animations
|
|
2858
|
+
- **Custom CSS Variables**: Replaced with standard Tailwind classes
|
|
2859
|
+
- **Complex State Management**: Simplified to essential state only
|
|
2860
|
+
- **Redundant Folders**: Removed simple/ folder with duplicate exports
|
|
2861
|
+
|
|
2862
|
+
#### 🎨 Modern CSS Features
|
|
2863
|
+
- **CSS-only Positioning**: Using `position: absolute` and `transform` for tooltips and dropdowns
|
|
2864
|
+
- **CSS Pseudo-elements**: Using `::before` for arrows and indicators
|
|
2865
|
+
- **CSS Data Attributes**: Using `data-placement` for component positioning
|
|
2866
|
+
- **CSS Transitions**: Smooth animations handled entirely by CSS
|
|
2867
|
+
- **Tailwind-First**: All styling using Tailwind utility classes
|
|
2868
|
+
|
|
2869
|
+
#### 📦 Package Improvements
|
|
2870
|
+
- **Cleaner Structure**: Removed unnecessary abstraction layers
|
|
2871
|
+
- **Direct Imports**: Components available from their natural locations
|
|
2872
|
+
- **Simplified APIs**: Just the essential props needed for functionality
|
|
2873
|
+
- **Better Performance**: Reduced JavaScript overhead and complexity
|
|
2874
|
+
|
|
2875
|
+
#### 🐛 Bug Fixes
|
|
2876
|
+
- **Accessibility**: Fixed label associations and ARIA attributes
|
|
2877
|
+
- **TypeScript**: Resolved all type errors and warnings
|
|
2878
|
+
- **Linting**: Fixed all ESLint and Svelte warnings
|
|
2879
|
+
- **Deprecation**: Replaced deprecated Lucide icons with emoji alternatives
|
|
2880
|
+
|
|
2881
|
+
### Breaking Changes
|
|
2882
|
+
- **Removed Components**: KeyValueForm, ContactForm, ModernForm, NotificationManager, ToastManager
|
|
2883
|
+
- **Simplified Props**: Many components now have fewer configuration options
|
|
2884
|
+
- **Removed Variants**: Reduced complex variant systems to essential options only
|
|
2885
|
+
- **Event Simplification**: Simplified event structures across all components
|
|
2886
|
+
|
|
2887
|
+
### Migration Guide
|
|
2888
|
+
- **Form Components**: Use the unified `Form` component instead of multiple form variants
|
|
2889
|
+
- **Complex Features**: Many complex features have been removed in favor of simplicity
|
|
2890
|
+
- **Styling**: Components now use standard Tailwind classes instead of custom CSS variables
|
|
2891
|
+
- **Imports**: Import components directly from atoms/, molecules/, organisms/ folders
|
|
2892
|
+
|
|
2893
|
+
## [1.1.1] - 2025-10-06
|
|
2894
|
+
|
|
2895
|
+
### Fixed
|
|
2896
|
+
- Updated generated TypeScript definitions for better type safety
|
|
2897
|
+
- Enhanced package structure with improved type exports
|
|
2898
|
+
- Fixed build consistency and versioning
|
|
2899
|
+
|
|
2900
|
+
### Changed
|
|
2901
|
+
- Updated internal build files and generated assets
|
|
2902
|
+
- Improved TypeScript declaration file generation
|
|
2903
|
+
- Enhanced package.json structure and exports
|
|
2904
|
+
|
|
2905
|
+
## [1.1.0] - 2025-10-05
|
|
2906
|
+
|
|
2907
|
+
### Added
|
|
2908
|
+
- Comprehensive TypeScript definitions built into the library
|
|
2909
|
+
- Self-contained type system - no custom type definitions needed in consumer apps
|
|
2910
|
+
- Enhanced component type safety with proper event handling
|
|
2911
|
+
- SSR-safe utilities for better SvelteKit compatibility
|
|
2912
|
+
|
|
2913
|
+
### Changed
|
|
2914
|
+
- Updated build configuration for proper SvelteKit compatibility
|
|
2915
|
+
- Removed SSR mode from library build (libraries should be client-side)
|
|
2916
|
+
- Enhanced package.json exports for better module resolution
|
|
2917
|
+
- Updated peer dependencies for Svelte 5 compatibility
|
|
2918
|
+
|
|
2919
|
+
### Fixed
|
|
2920
|
+
- Fixed runtime errors with `Cannot read properties of null (reading 'f')`
|
|
2921
|
+
- Fixed event handling for Svelte 5 compatibility
|
|
2922
|
+
- Fixed TypeScript type inference for component props and events
|
|
2923
|
+
- Fixed package exports for proper module resolution
|
|
2924
|
+
|
|
2925
|
+
### Removed
|
|
2926
|
+
- Removed need for custom type definitions in consumer applications
|
|
2927
|
+
- Removed unnecessary client-only component duplicates
|
|
2928
|
+
- Removed SSR wrapper components (not needed)
|
|
2929
|
+
|
|
2930
|
+
### Breaking Changes
|
|
2931
|
+
- None - this is a backward-compatible update
|
|
2932
|
+
|
|
2933
|
+
## [1.0.11] - Previous version
|
|
2934
|
+
- Initial release with basic components
|
|
2935
|
+
|