zabi-components 8.1.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 +1011 -0
- package/README.md +1 -1
- package/THEME.md +18 -5
- package/THEMING.md +308 -74
- package/dist/atoms/ActionPanel.svelte +1 -1
- package/dist/atoms/Avatar.svelte +19 -3
- package/dist/atoms/Avatar.svelte.d.ts +12 -0
- package/dist/atoms/Badge.svelte +37 -8
- package/dist/atoms/Badge.svelte.d.ts +6 -0
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +52 -15
- package/dist/atoms/Button.svelte.d.ts +3 -2
- package/dist/atoms/Card.svelte +181 -25
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/Checkbox.svelte +33 -0
- package/dist/atoms/Checkbox.svelte.d.ts +27 -0
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +1 -0
- package/dist/atoms/DateField.svelte +41 -3
- package/dist/atoms/DateField.svelte.d.ts +31 -3
- package/dist/atoms/FloatingActionButton.svelte +5 -3
- package/dist/atoms/FloatingActionButton.svelte.d.ts +1 -1
- package/dist/atoms/Heading.svelte +27 -1
- package/dist/atoms/Heading.svelte.d.ts +7 -0
- package/dist/atoms/IconButton.svelte +81 -9
- package/dist/atoms/IconButton.svelte.d.ts +13 -0
- package/dist/atoms/Input.svelte +10 -5
- package/dist/atoms/Input.svelte.d.ts +3 -2
- package/dist/atoms/List.svelte +3 -2
- package/dist/atoms/ListItem.svelte +31 -7
- package/dist/atoms/ListItem.svelte.d.ts +6 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +78 -17
- package/dist/atoms/Progress.svelte.d.ts +11 -1
- package/dist/atoms/Radio.svelte +32 -0
- package/dist/atoms/Radio.svelte.d.ts +26 -0
- package/dist/atoms/Rating.svelte +132 -6
- package/dist/atoms/Rating.svelte.d.ts +24 -3
- package/dist/atoms/Select.svelte +24 -26
- package/dist/atoms/Select.svelte.d.ts +12 -0
- package/dist/atoms/SelectionControl.svelte +99 -6
- package/dist/atoms/SelectionControl.svelte.d.ts +25 -0
- 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 +35 -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/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/TemporalField.svelte +342 -10
- package/dist/atoms/TemporalField.svelte.d.ts +20 -4
- package/dist/atoms/Textarea.svelte +100 -5
- package/dist/atoms/Textarea.svelte.d.ts +21 -0
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +46 -3
- package/dist/atoms/TimeField.svelte.d.ts +34 -3
- package/dist/atoms/Toast.svelte +6 -5
- package/dist/atoms/Toggle.svelte +61 -10
- package/dist/atoms/Toggle.svelte.d.ts +15 -0
- package/dist/atoms/Tooltip.svelte +20 -1
- package/dist/atoms/Tooltip.svelte.d.ts +7 -0
- package/dist/atoms/index.d.ts +21 -0
- package/dist/atoms/index.js +17 -0
- package/dist/atoms/selection-control.styles.d.ts +15 -4
- package/dist/atoms/selection-control.styles.js +16 -5
- package/dist/components/atoms/index.d.ts +21 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +15 -4
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +27 -3
- 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 +7 -2
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +30 -0
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +6 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +12 -3
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +55 -4
- package/dist/components/util/app-shell.d.ts.map +1 -1
- package/dist/components/util/avatar.d.ts +3 -0
- package/dist/components/util/avatar.d.ts.map +1 -1
- package/dist/components/util/bottom-tab-bar.d.ts +6 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -1
- 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 +6 -0
- package/dist/components/util/collapsible.d.ts.map +1 -1
- 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/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +6 -0
- package/dist/components/util/field.d.ts.map +1 -1
- package/dist/components/util/focus-utils.d.ts +2 -0
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- 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/photo.d.ts +19 -0
- package/dist/components/util/photo.d.ts.map +1 -1
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.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/sheet-drag.d.ts +25 -1
- package/dist/components/util/sheet-drag.d.ts.map +1 -1
- 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/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +40 -0
- package/dist/components/util/toaster.d.ts.map +1 -1
- 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/top-navbar.d.ts +26 -2
- package/dist/components/util/top-navbar.d.ts.map +1 -1
- package/dist/components/util/zabi-strings.d.ts +9 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -1
- package/dist/create-theme/cli.js +6 -2
- package/dist/create-theme/index.d.ts +17 -3
- package/dist/create-theme/index.js +252 -35
- package/dist/create-theme/lib/resolve.js +97 -0
- package/dist/create-theme/theme-data.js +1618 -90
- package/dist/index.d.ts +27 -3
- package/dist/index.js +18 -0
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +31 -12
- package/dist/molecules/Alert.svelte.d.ts +12 -1
- package/dist/molecules/AppBar.svelte +287 -28
- package/dist/molecules/AppBar.svelte.d.ts +62 -0
- package/dist/molecules/AvatarGroup.svelte +1 -1
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +88 -20
- package/dist/molecules/BottomSheet.svelte.d.ts +17 -1
- package/dist/molecules/BottomTabBar.svelte +134 -28
- package/dist/molecules/BottomTabBar.svelte.d.ts +36 -2
- package/dist/molecules/Calendar.svelte.d.ts +1 -1
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +158 -54
- package/dist/molecules/Collapsible.svelte.d.ts +23 -6
- package/dist/molecules/CollapsibleGroup.svelte +16 -6
- package/dist/molecules/ConfirmDialog.svelte +5 -1
- package/dist/molecules/ConfirmDialog.svelte.d.ts +3 -0
- package/dist/molecules/Drawer.svelte +33 -12
- package/dist/molecules/Dropdown.svelte +9 -1
- package/dist/molecules/Dropdown.svelte.d.ts +2 -0
- package/dist/molecules/DropdownItem.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +230 -120
- package/dist/molecules/ImageUpload.svelte.d.ts +38 -3
- 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/Modal.svelte +11 -6
- package/dist/molecules/PhotoGrid.svelte +100 -10
- package/dist/molecules/PhotoGrid.svelte.d.ts +11 -0
- package/dist/molecules/PhotoViewer.svelte +121 -24
- package/dist/molecules/PhotoViewer.svelte.d.ts +18 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/RadioGroup.svelte +20 -4
- package/dist/molecules/RadioGroup.svelte.d.ts +5 -0
- package/dist/molecules/SegmentedControl.svelte +136 -3
- package/dist/molecules/SegmentedControl.svelte.d.ts +6 -0
- package/dist/molecules/SidebarFooter.svelte +1 -1
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SortableList.svelte +1 -0
- package/dist/molecules/Stepper.svelte +21 -7
- package/dist/molecules/StickyActionBar.svelte +67 -8
- package/dist/molecules/StickyActionBar.svelte.d.ts +14 -1
- package/dist/molecules/Tabs.svelte +79 -2
- package/dist/molecules/Toaster.svelte +171 -7
- package/dist/molecules/Toaster.svelte.d.ts +60 -2
- package/dist/molecules/ToasterToast.svelte +292 -60
- package/dist/molecules/ToasterToast.svelte.d.ts +6 -0
- package/dist/molecules/UnsavedChangesBar.svelte +13 -2
- package/dist/molecules/index.d.ts +7 -2
- package/dist/molecules/index.js +4 -0
- package/dist/molecules/toast-store.d.ts +30 -0
- package/dist/molecules/toast-store.js +4 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +539 -39
- package/dist/organisms/AppShell.svelte.d.ts +102 -6
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarNavigation.svelte +23 -36
- package/dist/organisms/SidebarPanel.svelte +10 -14
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +2 -3
- package/dist/organisms/TopNavbar.svelte +117 -28
- package/dist/organisms/TopNavbar.svelte.d.ts +22 -1
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +6 -1
- package/dist/organisms/index.js +4 -0
- package/dist/types/components.d.ts +17 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +497 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +14 -3
- package/dist/util/app-shell.d.ts +55 -4
- package/dist/util/app-shell.js +32 -7
- package/dist/util/avatar.d.ts +3 -0
- package/dist/util/bottom-tab-bar.d.ts +6 -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 +6 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +6 -0
- package/dist/util/focus-utils.d.ts +2 -0
- package/dist/util/focus-utils.js +4 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/photo.d.ts +19 -0
- package/dist/util/photo.js +3 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/sheet-drag.d.ts +25 -1
- package/dist/util/sheet-drag.js +71 -1
- 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/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/toaster.d.ts +40 -0
- package/dist/util/toaster.js +55 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/top-navbar.d.ts +26 -2
- package/dist/util/top-navbar.js +23 -0
- package/dist/util/zabi-strings.d.ts +9 -0
- package/dist/zabi-components-colors.css +9 -3
- package/dist/zabi-components-theme-dark-only.css +474 -164
- package/dist/zabi-components-theme-dark.css +474 -164
- package/dist/zabi-components-theme-only.css +933 -99
- package/dist/zabi-components-theme.css +933 -99
- package/dist/zabi-components.css +2275 -321
- package/package.json +11 -1
package/THEMING.md
CHANGED
|
@@ -146,12 +146,16 @@ have your own ramps.
|
|
|
146
146
|
| `--zabi-accent-50` … `--zabi-accent-950` | the same 11 | `--color-accent` and its roles: `bg-accent`, `text-accent`, `border-accent`. Defaults to the citron ramp. |
|
|
147
147
|
| `--zabi-base-50` … `--zabi-base-950` | 50, 75, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 925, 950 | Text, borders, inputs, the page, cards and the four dark surface levels |
|
|
148
148
|
| `--zabi-on-brand` | | Label on a primary fill in light. Default `#ffffff`. |
|
|
149
|
-
| `--zabi-on-brand-dark` | | The same in dark. Default `var(--zabi-brand
|
|
149
|
+
| `--zabi-on-brand-dark` | | The same in dark. Default `var(--zabi-on-brand)` (white): the dark primary is the same step as light. Set a dark label here if the dark primary is a pale step. |
|
|
150
150
|
| `--zabi-on-accent` | | Label on a solid accent fill in light. Default `#ffffff`. |
|
|
151
151
|
| `--zabi-on-accent-dark` | | The same in dark. Default `var(--zabi-accent-950)`. |
|
|
152
152
|
|
|
153
153
|
Each ramp runs light (50) to dark (950). Light mode puts fills on step 600;
|
|
154
|
-
|
|
154
|
+
the primary and danger fills stay on step 600 in dark too (the physical
|
|
155
|
+
`--zabi-*` steps, with a white label), so the main action keeps its colour. The
|
|
156
|
+
other roles mirror the ramp in dark (the ring, links and tints are the light
|
|
157
|
+
steps there). `createTheme({ darkPrimary: "mirror" })` restores the 8.1 pale
|
|
158
|
+
dark fills.
|
|
155
159
|
|
|
156
160
|
A ramp you write by hand should keep the library's lightness per step, or the
|
|
157
161
|
contrast the roles were designed with is gone. Run it through
|
|
@@ -161,21 +165,28 @@ contrast the roles were designed with is gone. Run it through
|
|
|
161
165
|
|
|
162
166
|
| Token | Default | What follows |
|
|
163
167
|
|---|---|---|
|
|
164
|
-
| `--font-family-sans` |
|
|
168
|
+
| `--font-family-sans` | the platform UI face: `ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"` | Body text and every component |
|
|
165
169
|
| `--font-family-heading` | `var(--font-family-sans)` | `h1` to `h6` and the Heading component |
|
|
166
|
-
| `--font-family-
|
|
170
|
+
| `--font-family-display` | `var(--font-family-heading)` | `Heading variant="display"` and `font-display`: a second face, so it is a token and not a CSS rule |
|
|
171
|
+
| `--font-family-mono` | `ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace` | CodeBlock |
|
|
167
172
|
| `--font-weight-regular` | `400` | `font-normal` |
|
|
168
173
|
| `--font-weight-medium` | `500` | `font-medium` |
|
|
169
174
|
| `--font-weight-semibold` | `600` | `font-semibold` |
|
|
170
175
|
| `--font-weight-bold` | `700` | `font-bold` |
|
|
171
176
|
|
|
172
|
-
|
|
177
|
+
The default is the platform's own face (San Francisco on Apple devices, Segoe
|
|
178
|
+
UI on Windows, Roboto on Android), so a default install downloads no font. It
|
|
179
|
+
differs by platform, and text widths differ from 8.1's Nunito Sans. Loading any
|
|
180
|
+
other font files is up to the app. To get the 8.1 face back, load Nunito Sans
|
|
181
|
+
yourself and set
|
|
182
|
+
`--font-family-sans: 'Nunito Sans', ui-sans-serif, system-ui, sans-serif`
|
|
183
|
+
on `:root` after the imports. The heading face is applied in the
|
|
173
184
|
base layer, so a `font-*` utility on a heading still wins.
|
|
174
185
|
|
|
175
186
|
Tailwind's own font utilities follow these tokens: `font-sans` is
|
|
176
187
|
`--font-family-sans`, `font-mono` is `--font-family-mono`, and `font-heading`
|
|
177
|
-
is `--font-family-heading`. The theme maps Tailwind's `--font-sans` and
|
|
178
|
-
`--font-mono` (and a `--font-heading` of its own) onto them. Before 8.1
|
|
188
|
+
is `--font-family-heading`, `font-display` is `--font-family-display`. The theme maps Tailwind's `--font-sans` and
|
|
189
|
+
`--font-mono` (and a `--font-heading` and `--font-display` of its own) onto them. Before 8.1
|
|
179
190
|
`font-sans` was Tailwind's system stack, so `class="font-sans"` on a heading
|
|
180
191
|
dropped it out of the brand face. Set the `--font-family-*` tokens, not the
|
|
181
192
|
short names: the short ones follow.
|
|
@@ -189,14 +200,31 @@ short names: the short ones follow.
|
|
|
189
200
|
| `--radius-overlay` | `1rem` | Modals, sheets, menus, popovers, toasts |
|
|
190
201
|
| `--radius-pill` | `9999px` | Badges, avatars, status dots |
|
|
191
202
|
| `--shadow-color` | `24 24 27` (dark: `0 0 0`) | The colour of `shadow-sm` and `shadow-lg`, as three space-separated RGB channels |
|
|
192
|
-
| `--shadow-opacity` | `0.
|
|
193
|
-
| `--
|
|
203
|
+
| `--shadow-opacity` | `0.10` (dark: `0.24`) | The strength of the wide, soft layer of each shadow |
|
|
204
|
+
| `--shadow-opacity-contact` | `0.06` (dark: `0.3`) | The strength of the tight contact layer |
|
|
205
|
+
| `--z-dropdown`, `--z-sticky`, `--z-fixed`, `--z-modal-backdrop`, `--z-modal`, `--z-popover`, `--z-tooltip`, `--z-toast` | 1000 to 1080 | The layer of each floating component, when the app has its own stacking order to fit into. Each has a class of the same name (`z-sticky`, `z-fixed`, `z-modal`, …) that sets `z-index` from it |
|
|
194
206
|
|
|
195
|
-
The
|
|
207
|
+
The shadow tokens are the only ones here with a separate dark value. A
|
|
196
208
|
`:root` override after the imports replaces both; to keep two values, declare
|
|
197
209
|
the dark one under the dark selectors (see
|
|
198
210
|
[One role in dark only](#one-role-in-dark-only)).
|
|
199
211
|
|
|
212
|
+
### Motion
|
|
213
|
+
|
|
214
|
+
| Token | Default | What follows |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| `--duration-fast`, `--duration-base`, `--duration-moderate`, `--duration-slow` | 100, 150, 200, 300ms | `--duration-base` is Tailwind's default transition duration; use the others as `duration-(--duration-moderate)` |
|
|
217
|
+
| `--ease-standard` | `cubic-bezier(0.2, 0, 0, 1)` | Tailwind's default transition curve, and the `ease-standard` utility |
|
|
218
|
+
| `--ease-out` | `cubic-bezier(0, 0, 0.2, 1)` | The `ease-out` utility (Tailwind's own name and value) |
|
|
219
|
+
| `--ease-spring` | a `linear()` spring, one small overshoot | The `ease-spring` utility; browsers without `linear()` get a cubic-bezier with a similar overshoot |
|
|
220
|
+
|
|
221
|
+
Under `prefers-reduced-motion: reduce` the durations are `0s` and
|
|
222
|
+
`--ease-spring` is the standard curve. This re-points the tokens with
|
|
223
|
+
`:root:root`, so it also wins over an app's own value: to keep motion for
|
|
224
|
+
reduced-motion users, set the token inside your own
|
|
225
|
+
`@media (prefers-reduced-motion: reduce) { :root:root:root { … } }`. To get
|
|
226
|
+
Tailwind's old curve back, set `--default-transition-timing-function`.
|
|
227
|
+
|
|
200
228
|
### Not on the list
|
|
201
229
|
|
|
202
230
|
These tokens exist, and their names are kept, but setting them does not restyle
|
|
@@ -485,6 +513,17 @@ them, the danger ring included. They were, on a brand block in the default
|
|
|
485
513
|
theme: labels 2.15 (light) and 1.52 (dark), `outline` and `ghost` 3.65 and
|
|
486
514
|
2.05, `link` 1.39 and 1.34, the danger ring 1.06 and 1.04.
|
|
487
515
|
|
|
516
|
+
**A fill you choose.** `on-fill` is the same scope for a colour the library
|
|
517
|
+
does not know: `<div class="on-fill" style="background: #123; --zabi-on-fill: #fff">`.
|
|
518
|
+
Everything above applies with `--zabi-on-fill` as the label colour (without it
|
|
519
|
+
the label is `currentColor`, and the ring's gap is transparent, so the block
|
|
520
|
+
shows through). A surface inside (`bg-card`, `bg-card-tint`, `bg-chip`, `on-surface`
|
|
521
|
+
and the rest of the list) gets the theme's roles back. The library cannot hold
|
|
522
|
+
a colour the app picks at run time: the 4.5:1 of the label on your fill (3:1 for
|
|
523
|
+
edges and rings) is yours to check. A primary Button, a solid Badge and a
|
|
524
|
+
Rating keep their own fills and labels inside any of the three blocks; only the
|
|
525
|
+
text roles, the edges and the rings follow the block.
|
|
526
|
+
|
|
488
527
|
The Button variants, on a brand block (`bg-action-primary`). "Label" is
|
|
489
528
|
against the button's own fill, or against the block where the button has
|
|
490
529
|
none; "fill" is the button's fill against the block (3:1 for the button to be
|
|
@@ -590,6 +629,26 @@ anything around it; they inherit.
|
|
|
590
629
|
| `Rating` | `--zabi-rating-on-hover`, `--zabi-rating-on-active` | the same under the pointer and while pressed | `--zabi-rating-on` if you set it, else the tone's hover and pressed roles |
|
|
591
630
|
| `Rating` | `--zabi-rating-on-edge` | the outline of a filled star | the fill; `--color-accent-text` with `tone="accent"` |
|
|
592
631
|
| `Rating` | `--zabi-rating-off` | the outline of an empty star | `--color-control-border` |
|
|
632
|
+
| `Rating` | `--zabi-rating-off-fill` | an empty star with `empty="soft"` (below 3:1 on the page; pair it with a visible label and value) | `--color-control-track` |
|
|
633
|
+
| `SpeechBubble` | `--zabi-bubble-fill` | the bubble and its tail; wins over `tone` | the headline colour; the page colour with `tone="paper"` |
|
|
634
|
+
| `SpeechBubble` | `--zabi-bubble-text` | the quoted text; wins over `tone` (4.5:1 on your fill is yours to check) | the page colour; the headline colour with `tone="paper"` |
|
|
635
|
+
| `SpeechBubble` | `--zabi-bubble-radius` | not a colour: the bubble's corners, any `border-radius` value, the eight-value form included | `1.5rem` |
|
|
636
|
+
| `SpeechBubble` | `--zabi-bubble-tilt` | not a colour: turns the fill and the tail, never the text | `0deg` |
|
|
637
|
+
| `LineChart`, `BarChart`, `PieChart`, `Sparkline` | `--zabi-chart-1` | the first series: a line, the bars, the first slice (3:1 on the surface under it is yours to check) | `--color-action-primary`: the blue a `Progress` fills with |
|
|
638
|
+
| `LineChart`, `PieChart` | `--zabi-chart-2` | the second series | `--color-accent-500`: the accent at its middle step |
|
|
639
|
+
| `LineChart`, `PieChart` | `--zabi-chart-3` | the third series | `--color-brand-900`: the brand at its deepest step (its palest in dark) |
|
|
640
|
+
| `LineChart`, `PieChart` | `--zabi-chart-4` | the fourth series | `--color-accent-700`: the accent, deep (pale in dark) |
|
|
641
|
+
| `LineChart`, `PieChart` | `--zabi-chart-5` | the fifth series | `--color-base-500`: the neutral grey |
|
|
642
|
+
| `LineChart`, `PieChart` | `--zabi-chart-6` | the sixth series; a seventh starts again at the first | plum: `--color-brand-700` and `--color-error-700` mixed half and half in OKLCH, the one colour from outside the three ramps |
|
|
643
|
+
| `LineChart` | `--zabi-chart-surface` | paints the ring around each marker. Without it the ring is cut out of the lines, so it shows whatever the chart stands on (a card, a tint, glass) and there is nothing to set | none: the ring is see-through |
|
|
644
|
+
|
|
645
|
+
The series colours are the library's own: the brand, the accent and the neutral ramp, each at a solid step and a deep one, so a chart sits beside a `Progress` or a `Badge` without a colour of its own. They are categories, not states: none is `--color-success`, `--color-warning`, `--color-error` or `--color-info`. Because they are steps of your ramps they follow light and dark and a brand of your own; they were measured on the default theme only (each 3:1 or more on a card in both modes; any two apart for a reader who does not see red and green). When a series means a state (right, wrong, skipped) give it `color: 'success'`, `'error'`, `'neutral'`; a series without a `color` in the same chart then steps past the series colour nearest to a named one.
|
|
646
|
+
|
|
647
|
+
The rest of a chart is borrowed too, so it follows what you set for those: a `BarChart` with a `yMax` stands each bar in a track drawn with `--color-progress-track` and `--color-progress-track-border`; grid lines are `--color-border`; the rows of the `PieChart` key and of the narrow `LineChart` key are parted by the divider of a `List`; `showTable` shows a `Table`.
|
|
648
|
+
|
|
649
|
+
The slices of a `PieChart` and the bars of a `BarChart` are drawn as glass. An SVG shape takes no backdrop blur, so each keeps its series colour, opaque, and the depth is a soft gradient and a barely-there edge: the veil of a solid control (`--color-control-gradient-accent-start` to `--color-control-gradient-accent-end`: darken-only in light, light at the top in dark), one pixel of light on the top of the rim (the lightest neutral at 30%, the same in both modes), a blurred sheen under it at about a third of that, and a soft shadow under the drawing (the two shadows of `--shadow-md`; the contact shadow only under a bar). Retune the veil and shadow tokens and the charts follow. They are the flat fills wherever the materials are opaque: reduced transparency, more contrast, forced colours, and inside `data-materials="opaque"`, which is also how an app turns the look off for one chart or for all of them. On the default theme every fill stays 3:1 or more on a card under the veil in both modes. The key marks, line markers and the `Sparkline` dot stay flat: at 6 to 10px there is no room for it. The gaps of a full pie narrow to a point at the centre, so every slice runs to the middle; in a ring they have parallel sides.
|
|
650
|
+
|
|
651
|
+
The glass under a chart, as distinct from the marks, is the library's thick material and nothing of its own, so the material tokens and every fallback apply to it: reduced transparency, more contrast, forced colours, a browser without `backdrop-filter` and `data-materials="opaque"` all give the opaque surface. The pane of values that comes up over a `LineChart` when it is pointed at is always that material, as a `Tooltip` is. The `glass` prop on `LineChart`, `BarChart` and `PieChart` stands the whole chart on a panel of it; that is for a chart over the canvas wash, a tint card or a picture. On a plain card it shows only an edge and a shadow. Measured on the default theme, every series colour is 3:1 or more on the glass over the wash and over a tint card in both modes; over a solid brand or accent fill the second series falls to 2.9:1 in light and the first and fifth to 2.3:1 and 1.8:1 in dark, so put the chart on a `Card` there instead.
|
|
593
652
|
|
|
594
653
|
```svelte
|
|
595
654
|
<Rating label="Betyg" tone="accent" bind:value />
|
|
@@ -710,8 +769,9 @@ What it decides, and how:
|
|
|
710
769
|
neither, so both stay on the ramp. The header lists which followed.
|
|
711
770
|
- **Dark is not pinned unless the colour passes there.** `#0026EA` is 2.08:1
|
|
712
771
|
against a dark page and 1.77:1 against a dark card, where a fill needs 3:1:
|
|
713
|
-
no label makes that a button. Dark keeps
|
|
714
|
-
|
|
772
|
+
no label makes that a button. Dark keeps
|
|
773
|
+
step 600 (the mirrored step 400 with `darkPrimary: "mirror"`), exactly as
|
|
774
|
+
without `--pin`, and the header says so. A
|
|
715
775
|
colour that does pass in dark (the amber, the yellow) is pinned there too,
|
|
716
776
|
with lighter states.
|
|
717
777
|
|
|
@@ -738,9 +798,24 @@ modes; with `--set-light` or `--set-dark`, only in that mode.
|
|
|
738
798
|
- **Neutral ramp.** It keeps the base scale's own 21 lightness steps and takes
|
|
739
799
|
only the hue, at low chroma (0.03 at most). A saturated colour gives a tinted
|
|
740
800
|
grey, not a coloured page. `neutralChroma` lifts that cap.
|
|
801
|
+
The chroma tapers towards the light end of the ramp, so the lightest steps
|
|
802
|
+
are paler than the mid steps. To match an owner's page colour exactly, do not
|
|
803
|
+
raise `neutralChroma` (that tints the mid steps and muted text too); set the
|
|
804
|
+
page per mode instead: `overrides: { light: { '--color-surface-base': '#e3edff' } }`.
|
|
805
|
+
- **Links follow an overridden primary.** If `overrides` set `--color-action-primary` (and not `--color-link`), links take that colour in each mode the override applies to, and `--color-link-hover` the primary hover if you set one, 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. To put the library's link back: `overrides: { '--color-link': 'var(--color-brand-700)', '--color-link-hover': 'var(--color-brand-800)' }`.
|
|
741
806
|
- **"On" colours.** It sets `--zabi-on-brand` and `--zabi-on-brand-dark` (and
|
|
742
807
|
the accent pair when `--accent` is given) to white or the ramp's 950 step,
|
|
743
|
-
whichever reaches 4.5:1 on the fill, its hover and its active step.
|
|
808
|
+
whichever reaches 4.5:1 on the fill, its hover and its active step. In dark
|
|
809
|
+
the brand label tries white first (the dark primary is step 600), and falls
|
|
810
|
+
back to the 950 step when an app has moved the dark primary to a pale step.
|
|
811
|
+
- **`darkPrimary`.** `"brand"` (default) writes nothing: the library's dark
|
|
812
|
+
primary and danger are step 600 with a white label. `"mirror"` writes the 8.1
|
|
813
|
+
fills under the dark selectors: primary and danger on the mirrored 600, 700
|
|
814
|
+
and 800 (pale in dark), danger's label as it was, the label chosen by the
|
|
815
|
+
contrast check, and the control veil at 22% / 6%. An override of those roles
|
|
816
|
+
in `dark` or `both` always wins. CLI: `--dark-primary brand|mirror`. A
|
|
817
|
+
`--set --color-action-primary=...` that applies to both modes keeps its dark
|
|
818
|
+
value; move it under `--set-light` to take the dark default.
|
|
744
819
|
- **Contrast check.** Every pair the library holds itself to is resolved in
|
|
745
820
|
light and dark with the new ramps in place: 4.5:1 for text and
|
|
746
821
|
3:1 for focus rings and UI parts.
|
|
@@ -842,6 +917,7 @@ const { css, tokens, warnings, closest } = createTheme({
|
|
|
842
917
|
neutralChroma: 0.05, // optional, 0 to 0.1, needs neutral: a clearly tinted neutral; the ink roles follow it
|
|
843
918
|
overrides: { "--color-link": "var(--color-brand-800)" }, // optional; both modes. Or { light, dark, both }
|
|
844
919
|
pin: true, // optional: the exact brand on the primary action; or { brand: true, accent: true }
|
|
920
|
+
darkPrimary: "brand", // optional: "brand" (default, step 600 in dark too) or "mirror" (the 8.1 pale dark fills)
|
|
845
921
|
});
|
|
846
922
|
|
|
847
923
|
for (const warning of warnings) console.warn(warning.message);
|
|
@@ -853,9 +929,9 @@ writeFileSync("src/lib/brand.generated.css", css);
|
|
|
853
929
|
|
|
854
930
|
| Returned | |
|
|
855
931
|
|---|---|
|
|
856
|
-
| `css` | The stylesheet: a header comment and one `:root { … }` rule. With `pin`, `neutralChroma` or `light` / `dark` overrides, two dark rules follow it. |
|
|
932
|
+
| `css` | The stylesheet: a header comment and one `:root { … }` rule. With `pin`, `neutralChroma`, `darkPrimary: "mirror"` or `light` / `dark` overrides, two dark rules follow it. |
|
|
857
933
|
| `tokens` | Every declaration of the `:root` rule, name to value. |
|
|
858
|
-
| `darkTokens` | Only with `pin`, `neutralChroma` or `light` / `dark` overrides: every declaration of the dark rules. |
|
|
934
|
+
| `darkTokens` | Only with `pin`, `neutralChroma`, `darkPrimary: "mirror"` or `light` / `dark` overrides: every declaration of the dark rules. |
|
|
859
935
|
| `pinned` | Only with `pin`: `{ brand?, accent? }`, each `{ hex, light: { followed, onRamp, states }, dark: { pinned, followed, onRamp, states?, failed? } }`. |
|
|
860
936
|
| `warnings` | `{ type: "contrast", pair, mode, ratio, required, foreground, background, message }` for each role pair below AA, and `{ type: "input", option, message }` notes about the colours given. Empty when everything passes. |
|
|
861
937
|
| `closest` | `{ brand, accent?, neutral? }`, each `{ step, hex, deltaE, exact }`: where the input landed in its ramp. |
|
|
@@ -883,7 +959,7 @@ rule (`both`, then `light`); `darkTokens` holds what the dark rules say: the
|
|
|
883
959
|
|
|
884
960
|
It throws a `TypeError` when `brand` is missing, a colour is not hex,
|
|
885
961
|
`pin.accent` is set without `accent`, `neutralChroma` is not a number from 0
|
|
886
|
-
to 0.1 or is given without `neutral`, or an override is not a custom property
|
|
962
|
+
to 0.1 or is given without `neutral`, `darkPrimary` is not `"brand"` or `"mirror"`, or an override is not a custom property
|
|
887
963
|
name with a non-empty string value (the message names the key path, for example
|
|
888
964
|
`overrides.light["--color-link"]`). Without these options the result and the
|
|
889
965
|
file are exactly what they were before the option existed.
|
|
@@ -1006,8 +1082,8 @@ Each family exposes the same roles, built from the same steps:
|
|
|
1006
1082
|
| `--color-<family>-weak` | 700 | pressed / emphasis |
|
|
1007
1083
|
| `--color-<family>-medium` | 800 | strongest fill |
|
|
1008
1084
|
| `--color-<family>-strong` | 900 | darkest |
|
|
1009
|
-
| `--color-<family>-subtle` | 200 (dark:
|
|
1010
|
-
| `--color-<family>-border` | 300 (dark:
|
|
1085
|
+
| `--color-<family>-subtle` | 200 (dark: 16% of step 500 over the card surface) | tinted fill — badges, alerts |
|
|
1086
|
+
| `--color-<family>-border` | 300 (dark: 36% of step 500 over the card surface) | tinted edge |
|
|
1011
1087
|
| `--color-<family>-text` | 700 | text on a subtle fill or a page surface |
|
|
1012
1088
|
|
|
1013
1089
|
Families: `success`, `warning`, `error`, `info`, `energetic`, `neutral`.
|
|
@@ -1016,11 +1092,11 @@ Families: `success`, `warning`, `error`, `info`, `energetic`, `neutral`.
|
|
|
1016
1092
|
and `-active` (800) instead of `-weak` / `-medium` / `-strong`.
|
|
1017
1093
|
|
|
1018
1094
|
The tints are the one role that does not simply mirror. A step-100 fill is
|
|
1019
|
-
1.16:1 on a white card,
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
`base-200` in dark.
|
|
1095
|
+
1.16:1 on a white card, which is enough on the near-white 9.0 page once the
|
|
1096
|
+
tint has its edge, so light uses steps 100 and 200 for the fill and the edge.
|
|
1097
|
+
`.dark` restates 100 and 200 too (the mirror of those would be too heavy on a
|
|
1098
|
+
dark surface). `neutral` follows the same shape on the base ramp: `base-200`
|
|
1099
|
+
fill and `base-250` edge in light, `base-300` and `base-200` in dark.
|
|
1024
1100
|
|
|
1025
1101
|
Prefer the **subtle** trio (`-subtle` fill + `-border` edge + `-text` label) for
|
|
1026
1102
|
anything informational. Solid fills are for the one element on a screen that has
|
|
@@ -1033,21 +1109,48 @@ Every surface uses one of four semantic levels. Use these tokens instead of raw
|
|
|
1033
1109
|
| Level | Token / utility | Use for | Light (OKLCH L) | Dark (OKLCH L) |
|
|
1034
1110
|
|---|---|---|---|---|
|
|
1035
1111
|
| Base | `--color-surface-base` · `bg-surface-base` | Page / app shell (the darkest level in both themes) | `base-150` · 94 | `--zabi-base-900` `#18181b` · 21 |
|
|
1036
|
-
| Raised | `--color-surface-raised` · `bg-surface-raised` | Cards, panels, sidebars | `#ffffff` · 100 |
|
|
1037
|
-
| Elevated | `--color-surface-elevated` · `bg-surface-elevated` | Nested cards, hover and active fills | `base-100` · 97 |
|
|
1038
|
-
| Overlay | `--color-surface-overlay` · `bg-surface-overlay` | Modals, sheets, dropdown/select menus, navigation panels, toasts | `#ffffff` · 100 |
|
|
1039
|
-
| Inset | `--color-surface-inset` · `bg-surface-inset` | A recessed area on a raised surface: a well, a stat strip, a code sample inside a card | `base-100` · 97 |
|
|
1112
|
+
| Raised | `--color-surface-raised` · `bg-surface-raised` | Cards, panels, sidebars | `#ffffff` · 100 | `--zabi-base-850` `#1f1f22` · 24 |
|
|
1113
|
+
| Elevated | `--color-surface-elevated` · `bg-surface-elevated` | Nested cards, hover and active fills | `base-100` · 97 | `--zabi-base-800` `#27272a` · 27 |
|
|
1114
|
+
| Overlay | `--color-surface-overlay` · `bg-surface-overlay` | Modals, sheets, dropdown/select menus, navigation panels, toasts | `#ffffff` · 100 | `--zabi-base-800` `#27272a` · 27 (same tone as elevated; set apart by rim and shadow) |
|
|
1115
|
+
| Inset | `--color-surface-inset` · `bg-surface-inset` | A recessed area on a raised surface: a well, a stat strip, a code sample inside a card | `base-100` · 97 | `--zabi-base-900` `#18181b` · 21 (the page step) |
|
|
1040
1116
|
|
|
1041
1117
|
**Inset is not a fifth rung.** The four levels say how far a surface is lifted;
|
|
1042
|
-
inset says it is cut into the card it sits on.
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1118
|
+
inset says it is cut into the card it sits on. In light it is the ramp step
|
|
1119
|
+
below the raised surface, about 3 lightness points under it, and every text token
|
|
1120
|
+
passes AA on it. In dark it is the page step (3 points below the card). In
|
|
1121
|
+
light it shares `base-100` with `elevated`: under a white card the only
|
|
1122
|
+
direction left is down, so a nested card and a well are the same colour there.
|
|
1123
|
+
In dark the inset and the page are the same colour, so an inset area on the page
|
|
1124
|
+
needs an edge. `--color-input` no longer points at it in dark (it did up to
|
|
1125
|
+
9.0.0-alpha.5): a dark field is a translucent step of the neutral ramp, below.
|
|
1048
1126
|
Do not use `--color-input` for a well. It is white in light mode, because a
|
|
1049
1127
|
light field is a raised surface.
|
|
1050
1128
|
|
|
1129
|
+
**The dark field fill.** `--color-input` is base-50 at 7% over transparent in dark,
|
|
1130
|
+
`-hover` 10%, `-focus` 7%, `-active` 15%: a well lighter than whatever it sits on,
|
|
1131
|
+
with no rule per surface, as the dark flat card is a lighter layer. Measured on the
|
|
1132
|
+
default theme: the resting well is 1.20:1 off the page (#28282b), 1.22:1 off a card
|
|
1133
|
+
(#2e2e31) and 1.23:1 off the overlay (#363639); the pressed fill is 1.30:1 past
|
|
1134
|
+
the resting one on all three, the value text 7.34:1 or more and the placeholder
|
|
1135
|
+
4.08:1 or more on every fill (4.5:1 except pressed over the overlay, held to 4:1).
|
|
1136
|
+
`--color-input-placeholder` is 40% of base-350 into base-400 in dark (#ababb3, was
|
|
1137
|
+
base-600, #a1a1aa), because a lifted fill took the old one to 4.04:1 pressed on a
|
|
1138
|
+
card. The well is held at 1.15:1 or more off the page, a card and the overlay in
|
|
1139
|
+
dark, the hover step 1.08:1 and the pressed step 1.25:1 past rest, as pairs
|
|
1140
|
+
laid over what is behind the field (`behind`, `only: 'dark'`, `fgPaint`), also in
|
|
1141
|
+
`createTheme`'s check. The edge stays 3:1 against the surface outside the field
|
|
1142
|
+
(`--color-input-border`, unchanged); against the lifted fill it is 3.05:1 on the
|
|
1143
|
+
page, 2.79:1 on a card and 2.50:1 on the overlay (guarded at 3, 2.7 and 2.4).
|
|
1144
|
+
Inside a colour block (`.on-brand`, `.on-accent`, `.on-fill`) a field takes the
|
|
1145
|
+
opaque `--color-input-solid`, `-hover`, `-focus`, `-active` (in light the field's
|
|
1146
|
+
own values, in dark the old opaque field), as the flat card takes the opaque card.
|
|
1147
|
+
The progress track is unchanged (`--color-base-75` in dark). Restore the 9.0.0-alpha.5
|
|
1148
|
+
field: `overrides: { dark: { '--color-input': 'var(--color-surface-inset)',
|
|
1149
|
+
'--color-input-hover': 'var(--color-base-200)', '--color-input-focus':
|
|
1150
|
+
'var(--color-base-150)', '--color-input-active': 'color-mix(in srgb,
|
|
1151
|
+
var(--zabi-base-50) 10%, var(--color-surface-inset))', '--color-input-placeholder':
|
|
1152
|
+
'var(--color-base-600)' } }`.
|
|
1153
|
+
|
|
1051
1154
|
Supporting tokens:
|
|
1052
1155
|
- `--color-surface-overlay-hover` (`bg-surface-overlay-hover`): row and icon-button hover **inside** an overlay. It is lighter than the overlay in dark mode.
|
|
1053
1156
|
- `--color-border-overlay` (`border-border-overlay`): 1px edge on overlays. A 10% ink tint in light mode, so a white menu on a white card has an outline; a solid step in dark mode.
|
|
@@ -1058,39 +1161,47 @@ raised and separated from it by `shadow-lg` and the overlay edge. The page is
|
|
|
1058
1161
|
`base-150`, not `base-100`: at `base-100` a card was 1.10:1 on the page and a
|
|
1059
1162
|
nested card 1.04:1 on its parent.
|
|
1060
1163
|
|
|
1061
|
-
**The dark levels are
|
|
1062
|
-
`--zabi-base-900`,
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1164
|
+
**The dark levels are steps of the neutral ramp** (9.0, decisions D133/D134).
|
|
1165
|
+
The page is `--zabi-base-900`, the card `--zabi-base-850`, elevated and overlay
|
|
1166
|
+
`--zabi-base-800` (default `#18181b` / `#1f1f22` / `#27272a`, OKLCH L 21.0 /
|
|
1167
|
+
24.1 / 27.4): two tone steps in all. The overlay hover is one step lighter
|
|
1168
|
+
(`--zabi-base-750`, `#333338`) and the inset (a field, a well) is the page step.
|
|
1169
|
+
The ladder lives in `tokens/surface-ladder.js` (`SURFACE_STEP`); edit the steps
|
|
1066
1170
|
there and run `npm run sync:tokens`, never the values in `app.css`.
|
|
1067
1171
|
|
|
1068
|
-
|
|
1172
|
+
Each role is a plain alias of its step and nothing else:
|
|
1069
1173
|
|
|
1070
1174
|
```css
|
|
1071
1175
|
--color-surface-base: var(--zabi-base-900);
|
|
1072
|
-
--color-surface-raised:
|
|
1176
|
+
--color-surface-raised: var(--zabi-base-850);
|
|
1177
|
+
--color-surface-overlay: var(--zabi-base-800);
|
|
1073
1178
|
```
|
|
1074
1179
|
|
|
1075
|
-
That
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1180
|
+
That keeps the ramp's own hue and chroma. Until 8.1 the levels were a white
|
|
1181
|
+
wash mixed into the darkest neutral, which lost chroma at every level on a
|
|
1182
|
+
tinted neutral (`createTheme({ neutral, neutralChroma })`): the higher the
|
|
1183
|
+
surface, the greyer. Now `neutral: '#607296', neutralChroma: 0.08` gives a
|
|
1184
|
+
`#111828` page, `#18202f` card and `#202837` overlay, and a `--zabi-base-*`
|
|
1185
|
+
override carries all of them.
|
|
1186
|
+
|
|
1187
|
+
**An overlay is told apart by its edge and its shadow, not by being greyer.**
|
|
1188
|
+
Its tone equals elevated. `--color-material-rim` (14% of `--zabi-base-50`) laid
|
|
1189
|
+
over the overlay is 1.85:1 on the page, 1.72:1 on the card and 1.56:1 on the
|
|
1190
|
+
overlay itself, and the dark `--shadow-opacity` / `--shadow-opacity-contact`
|
|
1191
|
+
(0.5 / 0.4) are much stronger than light's. The decorative hairline
|
|
1192
|
+
`--color-border` is `--color-base-250` (`#333338`) in dark: 1.41:1 on the page
|
|
1193
|
+
and 1.31:1 on the card. A field on the page is told apart by its 3:1 edge
|
|
1194
|
+
(`--color-input-border`), which is guarded on the field, page, card, elevated
|
|
1195
|
+
and overlay surfaces in both themes.
|
|
1196
|
+
|
|
1197
|
+
Two things to know. The step distances depend on the app's neutrals: a ramp
|
|
1198
|
+
whose 900, 850 and 800 steps are far from the defaults can land outside the
|
|
1199
|
+
2 to 5 point window the guard holds. And wherever Tailwind compiles the dark
|
|
1200
|
+
file (the `zabi-components/css` bundle, or an app's own build) it may add a
|
|
1201
|
+
static fallback, the default hex, before an alias for browsers without
|
|
1202
|
+
`var()`-in-colour support.
|
|
1203
|
+
|
|
1204
|
+
**Why dark mode steps lightness, and so little of it:** a drop shadow simulates light blocked by a raised object, which reads on a light page. On a dark page the shadow is as dark as the background, so the signal comes from tone: each level is **lighter** than the one below it, by about 3 OKLCH lightness points, with the overlay at the elevated tone and set apart by its rim and a stronger shadow. Light mode keeps white surfaces and uses shadows for elevation.
|
|
1094
1205
|
|
|
1095
1206
|
**Rules:**
|
|
1096
1207
|
- Anything that floats above content must use a lighter level than what it floats over. Floating components (Modal, SlideUp, Dropdown/Select menu, NavigationMenu panel, Toaster/Toast) use `bg-surface-overlay`.
|
|
@@ -1098,15 +1209,17 @@ the default dark surfaces whatever the app overrides.
|
|
|
1098
1209
|
- Existing tokens remain as aliases: `background` → base, `card` / `surface-1` → raised, `surface-2` / `card-hover` → elevated, `surface-3` → overlay (dark), `card-elevated` → overlay (light) / elevated (dark).
|
|
1099
1210
|
|
|
1100
1211
|
**Enforced by** `scripts/check-surface-elevation.js`, which `validate-theme.js` runs during `npm run build:css`. It fails when:
|
|
1101
|
-
- dark
|
|
1102
|
-
-
|
|
1212
|
+
- a dark level, the overlay hover or the inset is not declared as one step of the ramp (`var(--zabi-base-N)` or `var(--color-base-N)`), for example a `color-mix()` with a wash;
|
|
1213
|
+
- dark lightness decreases base to raised to elevated to overlay, or raised is not lighter than the page;
|
|
1214
|
+
- raised, elevated and overlay use more than two distinct tones above the page, a tone step is outside 2 to 5 OKLCH L points, or the top is more than 8 points above the page;
|
|
1215
|
+
- the rim over the overlay is under 1.5:1 on the page or the card, or under 1.3:1 on the overlay; or the dark `--color-border` is under 1.2:1 on the page or the card;
|
|
1103
1216
|
- the light levels are not ordered base < elevated < raised, either step is under 2 points, or the overlay is darker than raised;
|
|
1104
1217
|
- the inset surface is less than 2 points below raised, or darker than the page, in either theme;
|
|
1105
1218
|
- a text token falls below AA on any level or on the inset surface;
|
|
1106
1219
|
- overlay hover or tooltip fills are darker than the overlay;
|
|
1107
1220
|
- a floating component paints a surface class below overlay.
|
|
1108
1221
|
|
|
1109
|
-
To retune the dark levels, edit `
|
|
1222
|
+
To retune the dark levels, edit `SURFACE_STEP` in `tokens/surface-ladder.js`, run `npm run sync:tokens`, and then `node scripts/check-surface-elevation.js`.
|
|
1110
1223
|
|
|
1111
1224
|
> The light surface tokens must stay **below** the `/* Background Colors */` marker in `@theme`. `scripts/sync-theme-tokens.js` regenerates everything between the base-scale aliases and that marker.
|
|
1112
1225
|
|
|
@@ -1159,7 +1272,7 @@ all five surfaces: page, card, inset, elevated and overlay. The colour a focus-r
|
|
|
1159
1272
|
uses must be a token in that list (`scripts/contrast-pairs.js`), so a ring
|
|
1160
1273
|
cannot read a raw ramp step.
|
|
1161
1274
|
`--color-input-border`, the edge of a form field, is held to 3:1 on the field
|
|
1162
|
-
fill (`--color-input
|
|
1275
|
+
fill (`--color-input`; over the page, a card and the overlay in dark, where the fill is translucent), the page and the card (WCAG 1.4.11). The same pairs are
|
|
1163
1276
|
in the list `createTheme` checks, so a generated theme that lets a field's edge
|
|
1164
1277
|
fall below 3:1 is reported. Run it after
|
|
1165
1278
|
re-pointing any token:
|
|
@@ -1201,6 +1314,110 @@ it separately.
|
|
|
1201
1314
|
|
|
1202
1315
|
Enforced by `scripts/check-control-geometry.js`.
|
|
1203
1316
|
|
|
1317
|
+
### Materials
|
|
1318
|
+
|
|
1319
|
+
Frosted glass is the default for the layer that **floats over content**: bars,
|
|
1320
|
+
sheets, menus. It is never for cards, lists, inputs or the page. A material is
|
|
1321
|
+
a surface at partial alpha with a backdrop blur, a light top edge, a 1px rim
|
|
1322
|
+
and a soft shadow, applied together by one class.
|
|
1323
|
+
|
|
1324
|
+
| Class | Fill | Use for | Text it may carry |
|
|
1325
|
+
|---|---|---|---|
|
|
1326
|
+
| `material-thin` | `--color-material-thin` (from `--color-surface-chrome`) | A single control floating on a bar | headline |
|
|
1327
|
+
| `material-regular` | `--color-material-regular` (from `--color-surface-chrome`) | Bars, the tab bar, a floating sidebar | headline, body, label |
|
|
1328
|
+
| `material-thick` | `--color-material-thick` (from `--color-surface-overlay`) | Sheets, menus, modals, toasts, tooltips | all five text roles |
|
|
1329
|
+
|
|
1330
|
+
Supporting tokens: `--material-alpha-thin|regular|thick`, `--material-backdrop-brightness`, `--material-blur-thin|regular|thick`
|
|
1331
|
+
(16, 24 and 32px), `--material-saturate`, `--color-material-highlight`,
|
|
1332
|
+
`--color-material-rim` and `--shadow-material`. The highlight and rim are mixes
|
|
1333
|
+
of the neutral ramp (`--zabi-base-50` and `--zabi-base-900`) and the shadow is
|
|
1334
|
+
built on `--shadow-color`, so a neutral override tints all of them. A class
|
|
1335
|
+
sets no position, radius or overflow: add `rounded-*` yourself. A component
|
|
1336
|
+
never writes `backdrop-blur-*` or `backdrop-filter` (`scripts/check-token-violations.js`
|
|
1337
|
+
fails it).
|
|
1338
|
+
|
|
1339
|
+
**The floor.** A material is judged over the worst backdrop of the mode, not
|
|
1340
|
+
alone: the fill is composited over `--zabi-base-950` (light) or `--zabi-base-50`
|
|
1341
|
+
(dark), the far ends of the neutral ramp, because a photo can be that dark or
|
|
1342
|
+
that bright. In dark that backdrop is first dimmed: the filters are
|
|
1343
|
+
`blur() saturate() brightness(var(--material-backdrop-brightness))`, the token is
|
|
1344
|
+
`1` in light (an identity) and `0.6` in dark, and `brightness()` multiplies the
|
|
1345
|
+
sRGB channels, so the brightest thing behind dark glass is step 50 times 0.6.
|
|
1346
|
+
This is what a browser paints, because every fallback below removes the filter
|
|
1347
|
+
and makes the fill opaque in the same breath. Only material pairs are dimmed. On
|
|
1348
|
+
that result the roles above need 4.5:1, and regular and thick need the focus ring
|
|
1349
|
+
at 3:1 (a thin control's ring is drawn around it, outside the material); the same
|
|
1350
|
+
roles are also held over the mode's own page. `npm run check:contrast` and
|
|
1351
|
+
`createTheme` both compute this, with the mode's own value of the token, so an
|
|
1352
|
+
app that sets `--material-backdrop-brightness` back to 1 in dark is warned if its
|
|
1353
|
+
alphas then fail. The alphas for thin, regular and thick are light 58, 84, 88
|
|
1354
|
+
and dark 60, 68, 72: each is the smallest whole percent that passes plus a
|
|
1355
|
+
margin (against the page-colour chrome the floors are thin 50 / 30, regular
|
|
1356
|
+
82 / 47 and thick 84 / 68, light / dark).
|
|
1357
|
+
|
|
1358
|
+
**Fallbacks.** The three fills become the opaque surface they are made from and
|
|
1359
|
+
the blur is removed under `prefers-reduced-transparency: reduce`,
|
|
1360
|
+
`prefers-contrast: more`, `forced-colors: active`, where `backdrop-filter` is
|
|
1361
|
+
unsupported, and wherever the app sets `data-materials="opaque"` on the root or
|
|
1362
|
+
any ancestor (a way out for a slow phone). The fallback re-points the tokens,
|
|
1363
|
+
so every use of them follows, in both modes and over an app's own fill. Under
|
|
1364
|
+
forced colors the rim is a 1px `CanvasText` outline. To set a fill per mode:
|
|
1365
|
+
`overrides: { light: { '--color-material-regular': … }, dark: { … } }`.
|
|
1366
|
+
|
|
1367
|
+
**Layers.** `backdrop-filter` makes an element the containing block of its fixed
|
|
1368
|
+
descendants, so a container that holds a Dropdown or a Tooltip puts the glass on
|
|
1369
|
+
a `::before` instead. `material-layer-thick` and `material-layer-regular` are the
|
|
1370
|
+
layer forms of thick and regular (same tokens, so every fallback applies): the
|
|
1371
|
+
element is positioned by you, never scrolls its own content, and keeps only the
|
|
1372
|
+
outer shadow.
|
|
1373
|
+
|
|
1374
|
+
**Bars.** `--color-surface-chrome` is the page colour in both modes, so a bar is
|
|
1375
|
+
flush with the page. `material-bar` is the bar surface: put it on a positioned
|
|
1376
|
+
element (never a scroller). At rest its `::before` layer is `--color-bar`, with
|
|
1377
|
+
no line and no blur; with `data-scrolled-under="true"` it is the regular material
|
|
1378
|
+
and a 1px `--color-bar-border` hairline on the content side (the bottom edge, or
|
|
1379
|
+
the top edge with `data-bar-edge="bottom"`). The fill and line fade over
|
|
1380
|
+
`--duration-moderate`. Under the fallbacks a scrolled bar is page coloured with the
|
|
1381
|
+
hairline (`CanvasText` in forced colors). Scope `--color-bar` on the element
|
|
1382
|
+
(`transparent`, `var(--color-bar-brand)`) and the layer follows. Tokens, each
|
|
1383
|
+
settable per mode:
|
|
1384
|
+
|
|
1385
|
+
| Token | Default | Use |
|
|
1386
|
+
|---|---|---|
|
|
1387
|
+
| `--color-bar` (read by `material-bar`) | `var(--color-surface-chrome)` | A bar's fill at rest |
|
|
1388
|
+
| `--color-bar-border` (`border-bar`) | headline at 10% | The hairline once content is under the bar; decorative |
|
|
1389
|
+
| `--color-bar-brand` (`bg-bar-brand`) | `var(--color-action-primary)` | An on-brand AppBar; `--color-on-brand` is held at 4.5:1 on it |
|
|
1390
|
+
| `--color-tabbar-active` (`bg-tabbar-active`) | `var(--color-nav-menu-active)` | The active tab's pill; `--color-nav-menu-item-active` is held at 4.5:1 on it |
|
|
1391
|
+
|
|
1392
|
+
`--color-bar` and headline are held at 4.5:1 as well. To get the raised bar fill
|
|
1393
|
+
back: `--color-surface-chrome: var(--color-surface-raised)`.
|
|
1394
|
+
|
|
1395
|
+
### Gradients
|
|
1396
|
+
|
|
1397
|
+
Two gradients, as tokens, so a rebrand carries through. Contrast is never measured against a gradient; the guard checks the worst stop of each.
|
|
1398
|
+
|
|
1399
|
+
- **Canvas.** `--gradient-canvas` is two soft radial washes from `--color-brand-300` and `--color-accent-300` (stops `--color-canvas-wash-brand|accent`, strength `--gradient-canvas-strength`, light 55%, dark 25%). Glass over a flat page shows nothing; put `bg-canvas` (page colour plus the wash) on a fixed-height shell or a scroll container, where the wash stays put as content moves. The library never applies it to `body`. Text roles, link, focus ring and field edge are held on each stop over the page.
|
|
1400
|
+
- **Solid controls.** `bg-control-gradient` (primary and danger; the veil is neutral) and `bg-control-gradient-accent` lay a veil over the control's own `background-color`: a light neutral at the top, a slight dark one at the bottom (`--color-control-gradient-start|end`, `--color-control-gradient-accent-start|end`), plus an inset top edge (`--shadow-control-highlight`). Hover and pressed fills still show through. The label is held at 4.5:1 on both ends over rest, hover and pressed, for primary, accent and danger. Progress does not take a gradient.
|
|
1401
|
+
- Light is darken-only: `--gradient-control-top-strength: 0%` (the top is the control's own colour, so the label keeps its full contrast and your brand colour is not altered) and `--gradient-control-bottom-strength: 14%`. Dark is 22% / 6%. The accent has strengths of its own, `--gradient-control-accent-top-strength` and `-bottom-strength`, which follow the shared ones.
|
|
1402
|
+
- The highlight is written to Tailwind's inset-shadow variable, so a `shadow-*` class and `focus-ring` on the same element still draw.
|
|
1403
|
+
- `createTheme` does not hand you a veil that fails: where the darkened bottom takes a dark label under 4.5:1 (a light accent), it flips that veil to lighten-only in that mode (top up to 14%, bottom 0); if no strength passes it writes both as 0%. A strength you set yourself is never moved, only reported.
|
|
1404
|
+
- Override per mode with `createTheme` `overrides` and restate dark; a strength too high is reported as a contrast warning.
|
|
1405
|
+
|
|
1406
|
+
### Buttons and the Toggle
|
|
1407
|
+
|
|
1408
|
+
Tokens and classes the 9.0 Button, Toggle and FloatingActionButton read. All settable like any role; dark is restated.
|
|
1409
|
+
|
|
1410
|
+
- **Tonal** (`bg-action-tonal`, `text-action-tonal`): `--color-action-tonal` (the brand tint), `-hover`, `-active`, and `--color-action-tonal-text` (brand step 800, 4.5:1 or more on all three fills in both modes; step 700 fails the pressed fill on a saturated brand).
|
|
1411
|
+
- **Outline edge** (`border-action-outline`): `--color-action-outline-border` and `-hover`, the field edge by default (3:1 on the page and a card in both modes). Inside `.on-brand` and `.on-accent` it is the block's label colour.
|
|
1412
|
+
- **Button corner and label weight**, apart from fields: `rounded-button` reads `--zabi-button-radius` and falls back to `--radius-control`; `font-button` (500) and `font-button-strong` (600) read `--zabi-button-font-weight`. Declared nowhere, so set them on any ancestor (or in `createTheme` `overrides`).
|
|
1413
|
+
- **Off Toggle** (`bg-toggle-track`, `ring-toggle-track`): `--color-toggle-track`, `-hover`, `-active` and `--color-toggle-track-border`. The track is a light tint; its edge is what holds 3:1 on the page and a card (WCAG 1.4.11). `--color-control-track*` is unchanged and still the Slider's. **Toggle knob** (`bg-toggle-thumb`, `bg-toggle-thumb-checked`): the default knob is `--color-toggle-knob` off (light base-500, dark base-700) and `--color-toggle-knob-checked` on (the primary fill's label colour), each 3:1 or more on every track fill it sits on, at rest, hovered and pressed (WCAG 1.4.11; guarded, 3.27:1 at the least in light off, 4.86:1 on). A theme sets those two, light and dark each in their own block, and `scripts/check-contrast.js` holds the pairs. `--color-toggle-thumb` and `--color-toggle-thumb-checked` are declared nowhere, so any ancestor can set them and they win: the first is the knob in both states (the checked state reads it when its own is unset), the second the knob when on only. Contrast of an override is yours to check. In forced colours the knob is a system colour and none of the tokens is read.
|
|
1414
|
+
- **Neutral subtle Badge** (`bg-badge-neutral`, `text-badge-neutral`): `--zabi-badge-neutral-fill` and `--zabi-badge-neutral-text`, declared nowhere, read in front of `--color-neutral-subtle` and `--color-neutral-text`. Set them on any ancestor or in the theme (light and dark each in their own block) and the neutral subtle `Badge` moves; `Alert variant="neutral"`, `Skeleton`, `AvatarGroup`, `Header` and the other badge variants do not. Unset, the badge is as it was. Inside `.on-brand`, `.on-accent` and `.on-fill` the text is the theme's body colour again, as for any neutral surface; the fill follows the property. Contrast of the pair is yours to check (the default is 8.2:1 in light).
|
|
1415
|
+
- **Skeleton on a colour block** (`tone="on-color"`, `bg-skeleton-on-color`): the fill is the surface's label colour at 24% (`color-mix(in srgb, currentColor 24%, transparent)`), so inside `.on-brand`, `.on-accent`, `.on-fill` or a `Card` with `tone` it follows the block's label colour in light and dark. `--zabi-skeleton-fill`, declared nowhere, replaces it on any ancestor. The class is not in the lists that restore the theme's text on a surface inside a block, on purpose. The default `tone="neutral"` is `bg-neutral-subtle`, unchanged.
|
|
1416
|
+
- **Chip** (`bg-chip`, `text-chip-text`): `--color-chip`, `-hover`, `-active` (light base-200, 300, 350; dark base-750, 700, 650) and `--color-chip-text` (the headline, 4.5:1 on all three). Seen on the page and a card without an edge, 1.2:1 or more, guarded. **Selected chip** (`bg-chip-selected`, `text-chip-selected-text`): `--color-chip-selected`, `-hover`, `-active`, `-text`, the tonal role by default, as tokens of their own, with the label held on all three fills.
|
|
1417
|
+
- **Tint card** (`bg-card-tint`): `--color-card-tint`, brand-100 in light and the brand at 8% over the card in dark. Headline, body, label, description, caption and link are held to 4.5:1 on it, and the ring, the field edge and the outline Button's edge to 3:1, in both modes.
|
|
1418
|
+
- **Flat card** (`bg-card-flat`): `--color-card-flat`, `-hover`, `-active`. Light is the card, hover and pressed fills, opaque, as before. Dark is the neutral ramp's base-50 at 7%, 11% and 15% over transparent, so a flat card is a lighter layer of whatever is behind it: on the page (#18181b) one layer is #282829, on a card (#1f1f22) #2e2e31, on the overlay (#27272a) #363638, and each nested layer is another 1.2:1 to 1.25:1 step. It is derived from the ramp, so `neutralChroma` tints it. Restore the opaque fill with `overrides: { dark: { '--color-card-flat': 'var(--color-card)' } }` (and `-hover`, `-active` for an interactive flat card). Held in dark: the five text roles, the link and the ring on three layers over the card, two over the overlay and one over the page (the caption is 4.1:1 on three over the overlay, so that stack is not held), and the field edge and the outline Button's edge at 3:1 on one layer over the page (3.03:1; on a layer over a card or an overlay they are 2.8:1 or less, so a field there keeps its own fill). Inside a `tone` card (`.on-brand`, `.on-accent`, `.on-fill`) a flat card is the opaque card with the theme's text, in dark too, as `bg-card` was: a translucent step over the block's fill would carry the theme's text at about 2.3:1, and the label colour at 4.4:1.
|
|
1419
|
+
- **Progress track edge** (`border-progress-track-border`): `--color-progress-track-border`, the field edge by default (3:1 on the page and a card in both modes, because the default track is the field fill and does not reach it). `transparent` skips the guard. `createTheme` writes `transparent` for a mode in which you override `--color-progress-track` with a colour that itself reaches 3:1 on the page and the card, unless you set the edge; an edge you set, `transparent` included, is never moved.
|
|
1420
|
+
|
|
1204
1421
|
### Shadow scale
|
|
1205
1422
|
|
|
1206
1423
|
Elevation in light mode is **two steps**, not a ramp. A shadow says "this is
|
|
@@ -1225,8 +1442,10 @@ Rules:
|
|
|
1225
1442
|
`app.css` never defines, so no token controls it.
|
|
1226
1443
|
- Shadows carry elevation in **light mode only**. In dark mode the surface
|
|
1227
1444
|
levels above do the work — see [Surface elevation levels](#surface-elevation-levels).
|
|
1228
|
-
- The light shadow is the ramp's own ink (`--shadow-color: 24 24 27`)
|
|
1229
|
-
|
|
1445
|
+
- The light shadow is the ramp's own ink (`--shadow-color: 24 24 27`). Every
|
|
1446
|
+
step is two layers: a tight contact shadow at `--shadow-opacity-contact`
|
|
1447
|
+
(0.06; dark 0.3) and a wider, softer one at `--shadow-opacity` (0.10; dark
|
|
1448
|
+
0.24). Until 8.1 both layers shared one flat 0.14 (dark 0.3).
|
|
1230
1449
|
|
|
1231
1450
|
Enforced by `scripts/check-token-violations.js`.
|
|
1232
1451
|
|
|
@@ -1315,7 +1534,7 @@ Parts of `src/app.css` are generated too, so edit the source instead:
|
|
|
1315
1534
|
|---|---|---|
|
|
1316
1535
|
| A chromatic ramp (`brand`, `citron`, `pine`, `iris`, `warning`, `error`) | `hue` / `peakChroma` in `tokens/chromatic-scales.js` | `npm run sync:tokens` |
|
|
1317
1536
|
| The neutral ramp | `tokens/base-scale.js` | `npm run sync:tokens` |
|
|
1318
|
-
| The dark surface levels | `
|
|
1537
|
+
| The dark surface levels | `SURFACE_STEP` in `tokens/surface-ladder.js` | `npm run sync:tokens` |
|
|
1319
1538
|
| A role, a font, a radius | `src/app.css` | nothing in development |
|
|
1320
1539
|
|
|
1321
1540
|
Before a release, `npm run build:css` regenerates the `dist/` files and
|
|
@@ -1355,18 +1574,33 @@ three, or if the `auto` copy differs from `.dark`.
|
|
|
1355
1574
|
|
|
1356
1575
|
### Dark mode action colours
|
|
1357
1576
|
|
|
1358
|
-
- **Primary.**
|
|
1359
|
-
|
|
1360
|
-
|
|
1577
|
+
- **Primary.** The same physical steps as light: `.dark` points
|
|
1578
|
+
`--color-action-primary`, `-hover` and `-active` at `--zabi-brand-600`, `700`
|
|
1579
|
+
and `800` (not the mirrored `--color-brand-*`, which are the pale 400, 300
|
|
1580
|
+
and 200). The label is `--color-on-brand`, which `.dark` points at
|
|
1581
|
+
`--zabi-on-brand-dark` (white). The fill is 3.72:1 on the dark page, 3.43:1
|
|
1582
|
+
on a card and 3.10:1 on the overlay, held by guarded pairs. The ring, links,
|
|
1583
|
+
the tonal roles and the nav-active roles stay on the mirrored steps (they are
|
|
1584
|
+
text, rings and tints on a dark surface).
|
|
1585
|
+
`--color-primary*` is unchanged (still mirrored; no component reads it).
|
|
1586
|
+
- **Status tints.** In dark, `--color-<success|warning|error|info|energetic|accent>-subtle`
|
|
1587
|
+
is the hue thinned into the card surface: `color-mix(in srgb, var(--zabi-<ramp>-500) 16%, var(--color-surface-raised))`;
|
|
1588
|
+
`-border` is the same mix at 36%. A badge or alert is a low-chroma tint of its
|
|
1589
|
+
card (never darker than it) and the hue is carried by the icon and the text,
|
|
1590
|
+
which are unchanged. Because the base is a `var()`, an app's override of the
|
|
1591
|
+
card surface (or `neutralChroma`) carries through. `neutral` and the brand
|
|
1592
|
+
tints are unchanged.
|
|
1361
1593
|
- **Secondary.** No dark override: `--color-base-*` mirrors the same way.
|
|
1362
|
-
- **Danger.**
|
|
1363
|
-
|
|
1594
|
+
- **Danger.** The same physical steps as light danger (`--zabi-error-600`,
|
|
1595
|
+
`700`, `800`, label `--zabi-error-50`), so a destructive button is one red in
|
|
1596
|
+
both modes. Its `-subtle` tint is the error tint (`--color-error-subtle`), with
|
|
1597
|
+
`-subtle-hover` (20%) and `-subtle-active` (32%) stronger mixes. Restated in `.dark` so that
|
|
1364
1598
|
`zabi-components/theme-dark-only` remains a complete standalone import
|
|
1365
1599
|
(`validate-theme.js` enforces that).
|
|
1366
1600
|
|
|
1367
|
-
Hover and active
|
|
1368
|
-
|
|
1369
|
-
themes. Never point `-active` at a step on the far side of the ramp; that is
|
|
1601
|
+
Hover and active move toward the dark end for the primary and danger fills in
|
|
1602
|
+
both modes (one recipe). For the mirrored roles the mirror flips that, so "more
|
|
1603
|
+
pressed" means "more contrast against the page" in both themes. Never point `-active` at a step on the far side of the ramp; that is
|
|
1370
1604
|
how the pressed primary label once ended up at 1.66:1.
|
|
1371
1605
|
|
|
1372
1606
|
### Building the generator
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* variants.)
|
|
60
60
|
*/
|
|
61
61
|
const interactiveClasses =
|
|
62
|
-
"bg-linear-to-b from-transparent to-transparent hover:border-border-medium hover:from-surface-hover hover:to-surface-hover active:scale-[0.99] active:from-surface-active active:to-surface-active";
|
|
62
|
+
"cursor-pointer bg-linear-to-b from-transparent to-transparent hover:border-border-medium hover:from-surface-hover hover:to-surface-hover active:scale-[0.99] active:from-surface-active active:to-surface-active";
|
|
63
63
|
|
|
64
64
|
const disabledClasses = "opacity-50 cursor-not-allowed pointer-events-none";
|
|
65
65
|
|