vunor 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +6 -11
  2. package/dist/AppLayout.mjs +0 -0
  3. package/dist/AppToasts.mjs +4 -8
  4. package/dist/Button.mjs +5 -10
  5. package/dist/ButtonBase.mjs +4 -8
  6. package/dist/Calendar.mjs +10 -14
  7. package/dist/Card.mjs +4 -9
  8. package/dist/CardHeader.mjs +5 -10
  9. package/dist/CardInner.mjs +4 -9
  10. package/dist/Checkbox.mjs +4 -8
  11. package/dist/Combobox.d.mts +9 -9
  12. package/dist/Combobox.mjs +5 -9
  13. package/dist/DatePicker.d.mts +9 -9
  14. package/dist/DatePicker.mjs +12 -17
  15. package/dist/DatePickerBase.d.mts +9 -9
  16. package/dist/DatePickerBase.mjs +13 -17
  17. package/dist/DatePickerInner.mjs +5 -9
  18. package/dist/DatePickerPopup.mjs +4 -8
  19. package/dist/DelayedSwitch.mjs +4 -9
  20. package/dist/DevTools.mjs +583 -150
  21. package/dist/Dialog.d.mts +6 -6
  22. package/dist/Dialog.mjs +4 -8
  23. package/dist/Icon.mjs +4 -9
  24. package/dist/InnerLoading.mjs +4 -8
  25. package/dist/Input.d.mts +13 -13
  26. package/dist/Input.mjs +4 -8
  27. package/dist/InputBase.d.mts +9 -9
  28. package/dist/InputBase.mjs +4 -8
  29. package/dist/Label.mjs +1 -4
  30. package/dist/LoadingIndicator.mjs +4 -8
  31. package/dist/Menu.d.mts +2 -2
  32. package/dist/Menu.mjs +5 -14
  33. package/dist/MenuItem.mjs +4 -9
  34. package/dist/OverflowContainer.mjs +4 -8
  35. package/dist/Pagination.mjs +4 -9
  36. package/dist/Popover.mjs +4 -9
  37. package/dist/ProgressBar.mjs +4 -9
  38. package/dist/RadioGroup.mjs +4 -8
  39. package/dist/Select.d.mts +9 -9
  40. package/dist/Select.mjs +4 -9
  41. package/dist/SelectBase.mjs +4 -8
  42. package/dist/Slider.mjs +4 -8
  43. package/dist/Tabs.mjs +4 -9
  44. package/dist/nuxt.mjs +1 -3
  45. package/dist/theme.d.mts +35 -0
  46. package/dist/theme.mjs +289 -336
  47. package/dist/utils-6bTTIoaw.js +40 -0
  48. package/dist/utils.d.mts +2 -2
  49. package/dist/utils.mjs +1 -4
  50. package/dist/vite.mjs +1 -2
  51. package/dist/vunor.d.mts +13 -13
  52. package/dist/vunor.mjs +1 -6
  53. package/package.json +30 -37
  54. package/scripts/setup-skills.js +0 -78
  55. package/skills/vunor/SKILL.md +0 -115
  56. package/skills/vunor/components.md +0 -320
  57. package/skills/vunor/core.md +0 -173
  58. package/skills/vunor/forms.md +0 -348
  59. package/skills/vunor/palette.md +0 -223
  60. package/skills/vunor/rules.md +0 -263
  61. package/skills/vunor/shortcuts.md +0 -239
  62. package/skills/vunor/typography.md +0 -204
@@ -1,348 +0,0 @@
1
- # Form components — vunor
2
-
3
- > Detailed usage for Input, Select, Combobox, Checkbox, RadioGroup, Slider, and DatePicker.
4
-
5
- ## Concepts
6
-
7
- Form components use the `i8-*` shortcut system for visual styling and Reka UI primitives for accessibility. They support three design variants (`flat`, `filled`, `round`) and integrate with the color scope system.
8
-
9
- All form components:
10
- - Use `scope-{color}` for theming (default scope applies if none set)
11
- - Support `disabled`, `error`, and `loading` states
12
- - Emit standard Vue events (`update:modelValue`, `blur`, `focus`)
13
- - Use `aria-*` and `data-*` attributes for state (not HTML attributes)
14
-
15
- ## API Reference
16
-
17
- ### `<VuInput>`
18
-
19
- Text / textarea input with floating label.
20
-
21
- | Prop | Type | Default | Description |
22
- |------|------|---------|-------------|
23
- | `modelValue` | `string` | — | v-model binding |
24
- | `label` | `string` | — | Floating label text |
25
- | `stackLabel` | `boolean` | `false` | Keep label stacked (never floats) |
26
- | `placeholder` | `string` | — | Input placeholder |
27
- | `design` | `'flat' \| 'filled' \| 'round'` | `'flat'` | Visual design variant |
28
- | `type` | `'text' \| 'textarea'` | `'text'` | Input type |
29
- | `readonly` | `boolean` | `false` | Read-only state |
30
- | `disabled` | `boolean` | `false` | Disabled state |
31
- | `required` | `boolean` | `false` | Required field |
32
- | `maxlength` | `number` | — | Character limit |
33
- | `rows` | `number` | — | Textarea rows |
34
- | `autoGrow` | `boolean` | `false` | Auto-grow textarea |
35
- | `loading` | `boolean` | `false` | Show loading state |
36
- | `error` | `string \| boolean` | — | Error state / message |
37
- | `hint` | `string` | — | Hint text below input |
38
- | `iconBefore` | `string` | — | Icon before input (outside border) |
39
- | `iconAfter` | `string` | — | Icon after input (outside border) |
40
- | `iconPrepend` | `string` | — | Icon prepended inside border |
41
- | `iconAppend` | `string` | — | Icon appended inside border |
42
-
43
- **Emits**: `update:modelValue`, `blur`, `focus`, `click`, `beforeClick`, `afterClick`, `prependClick`, `appendClick`
44
-
45
- **Slots**: `default`, `before`, `after`, `prepend`, `append`, `input`, `overlay`, `error`, `hint`, `counter`, `label`
46
-
47
- **CSS classes**: `i8` (wrapper), `i8-input`, `i8-textarea`, `i8-label`, `i8-hint`, `i8-counter`, `i8-prepend`, `i8-append`, `i8-before`, `i8-after`, `i8-underline`
48
-
49
- **Data attrs**: `data-has-value`, `data-active`, `data-error`, `data-has-label`, `data-has-prepend`, `data-has-append`
50
-
51
- ```html
52
- <!-- Basic text input -->
53
- <VuInput v-model="name" label="Full Name" />
54
-
55
- <!-- With design variant -->
56
- <VuInput v-model="email" label="Email" design="filled" />
57
-
58
- <!-- With validation error -->
59
- <VuInput v-model="password" label="Password" type="password" :error="passwordError" />
60
-
61
- <!-- Textarea with auto-grow -->
62
- <VuInput v-model="bio" label="Bio" type="textarea" auto-grow :rows="3" />
63
-
64
- <!-- With icons -->
65
- <VuInput v-model="search" label="Search" icon-prepend="i-mdi-magnify" icon-append="i-mdi-close" />
66
-
67
- <!-- With character counter -->
68
- <VuInput v-model="tweet" label="Tweet" :maxlength="280" />
69
- ```
70
-
71
- ### `<VuSelect>`
72
-
73
- Dropdown select built on Reka UI SelectRoot.
74
-
75
- | Prop | Type | Default | Description |
76
- |------|------|---------|-------------|
77
- | `modelValue` | `string` | — | v-model binding |
78
- | `items` | `Array<string \| T> \| Record<string, Array>` | — | Options (array or grouped record) |
79
- | `defaultValue` | `string` | — | Initial value |
80
- | `disabled` | `boolean` | `false` | Disabled state |
81
- | `disabledValues` | `string[]` | — | Specific disabled options |
82
- | `required` | `boolean` | `false` | Required field |
83
- | `placeholder` | `string` | — | Placeholder text |
84
- | `popupClass` | `string` | — | CSS class for popup |
85
- | `popupRound` | `boolean` | — | Round popup corners |
86
- | `popupPosition` | `'item-aligned' \| 'popper'` | — | Popup positioning mode |
87
-
88
- **Slots**: `default` (custom item rendering), `selected-items`
89
- **CSS classes**: `select-content` (popup), `select-item`, `select-grp-label`, `select-separator`
90
- **Data attrs on items**: `data-highlighted`, `data-disabled`, `data-state="checked"`
91
-
92
- ```html
93
- <!-- Simple string items -->
94
- <VuInput label="Fruit" design="filled">
95
- <VuSelect v-model="fruit" :items="['Apple', 'Banana', 'Cherry']" />
96
- </VuInput>
97
-
98
- <!-- Object items -->
99
- <VuInput label="Country">
100
- <VuSelect
101
- v-model="country"
102
- :items="[
103
- { value: 'us', label: 'United States' },
104
- { value: 'ca', label: 'Canada' },
105
- { value: 'mx', label: 'Mexico' },
106
- ]"
107
- />
108
- </VuInput>
109
-
110
- <!-- Grouped items -->
111
- <VuInput label="City">
112
- <VuSelect
113
- v-model="city"
114
- :items="{
115
- 'North America': ['New York', 'Toronto'],
116
- 'Europe': ['London', 'Paris'],
117
- }"
118
- />
119
- </VuInput>
120
- ```
121
-
122
- **Important**: `VuSelect` is typically placed inside a `VuInput` wrapper to get the label, design variant, and error handling.
123
-
124
- ### `<VuCombobox>`
125
-
126
- Searchable dropdown with filtering. Inherits input props for label, design, etc.
127
-
128
- | Prop | Type | Default | Description |
129
- |------|------|---------|-------------|
130
- | `modelValue` | `string \| string[]` | — | v-model binding |
131
- | `items` | `Array<T \| string> \| Record<string, Array>` | — | Options |
132
- | `searchTerm` | `string` | — | v-model for search text |
133
- | `multiple` | `boolean` | `false` | Multi-select mode |
134
- | `checkboxItems` | `boolean` | `false` | Show checkboxes in multi-select |
135
- | `modelOpen` | `boolean` | — | v-model for open state |
136
- | `dropdownIcon` | `string` | `'i--chevron-down'` | Dropdown toggle icon |
137
- | `resetSearchTermOnBlur` | `boolean` | — | Clear search on blur |
138
- | `popupClass` | `string` | — | CSS class for popup |
139
- | `popupRound` | `boolean` | — | Round popup corners |
140
- | `align` | `string` | — | Popup alignment |
141
-
142
- Also accepts all `VuInput` props: `label`, `design`, `disabled`, `error`, `hint`, etc.
143
-
144
- **Slots**: `empty`, `group-label`, `item` (with `{ selected }` bind), `prepend`, `append`
145
-
146
- ```html
147
- <!-- Basic searchable select -->
148
- <VuCombobox v-model="fruit" label="Fruit" :items="fruits" />
149
-
150
- <!-- Multi-select with checkboxes -->
151
- <VuCombobox
152
- v-model="selectedFruits"
153
- label="Fruits"
154
- :items="fruits"
155
- multiple
156
- checkbox-items
157
- />
158
-
159
- <!-- Custom item rendering -->
160
- <VuCombobox v-model="user" label="User" :items="users">
161
- <template #item="{ item, selected }">
162
- <div class="flex items-center gap-$xs">
163
- <img :src="item.avatar" class="size-6 rounded-full" />
164
- <span>{{ item.label }}</span>
165
- </div>
166
- </template>
167
- </VuCombobox>
168
- ```
169
-
170
- ### `<VuCheckbox>`
171
-
172
- Checkbox with indeterminate state support.
173
-
174
- | Prop | Type | Default | Description |
175
- |------|------|---------|-------------|
176
- | `modelValue` | `boolean \| undefined \| 'indeterminate'` | — | v-model binding |
177
- | `label` | `string` | — | Checkbox label |
178
- | `disabled` | `boolean` | `false` | Disabled state |
179
- | `readonly` | `boolean` | `false` | Read-only state |
180
- | `required` | `boolean` | `false` | Required field |
181
- | `error` | `string \| boolean` | — | Error state / message |
182
- | `verticalMiddle` | `boolean` | `false` | Vertically center checkbox with label |
183
- | `reverse` | `boolean` | `false` | Label before checkbox |
184
-
185
- **Slots**: `default`, `label`
186
- **CSS classes**: `checkbox-root`, `checkbox`, `checkbox-indicator`, `checkbox-icon`, `checkbox-label`
187
- **Data attrs**: `data-state="checked" \| "unchecked" \| "indeterminate"`, `data-error`, `aria-disabled`
188
-
189
- ```html
190
- <VuCheckbox v-model="agreed" label="I agree to the terms" />
191
- <VuCheckbox v-model="selectAll" label="Select all" />
192
- ```
193
-
194
- ### `<VuRadioGroup>`
195
-
196
- Radio button group.
197
-
198
- | Prop | Type | Default | Description |
199
- |------|------|---------|-------------|
200
- | `modelValue` | `string` | — | v-model binding |
201
- | `items` | `Array<string \| { value, label?, disabled? }>` | — | Radio options |
202
- | `defaultValue` | `string` | — | Initial value |
203
- | `label` | `string` | — | Group label |
204
- | `labelVisible` | `boolean` | `true` | Show group label |
205
- | `row` | `boolean` | `false` | Horizontal layout |
206
- | `disabled` | `boolean` | `false` | Disable all items |
207
- | `disabledValues` | `string[]` | — | Specific disabled items |
208
- | `error` | `string \| boolean` | — | Error state / message |
209
- | `verticalMiddle` | `boolean` | `false` | Vertically center radio with label |
210
- | `reverse` | `boolean` | `false` | Label before radio |
211
-
212
- **Slots**: `default` (custom item rendering with item bind)
213
- **CSS classes**: `rb-container`, `rb-label`, `rb-root`, `rb-row`, `rb-item-wrapper`, `rb-item`, `rb-item-indicator`, `rb-item-label`
214
- **Data attrs**: `data-state`, `data-disabled`
215
-
216
- ```html
217
- <!-- String items -->
218
- <VuRadioGroup v-model="size" label="Size" :items="['Small', 'Medium', 'Large']" />
219
-
220
- <!-- Object items with disabled -->
221
- <VuRadioGroup
222
- v-model="plan"
223
- label="Plan"
224
- :items="[
225
- { value: 'free', label: 'Free' },
226
- { value: 'pro', label: 'Pro' },
227
- { value: 'enterprise', label: 'Enterprise', disabled: true },
228
- ]"
229
- />
230
-
231
- <!-- Horizontal layout -->
232
- <VuRadioGroup v-model="align" :items="['Left', 'Center', 'Right']" row />
233
- ```
234
-
235
- ### `<VuSlider>`
236
-
237
- Range slider.
238
-
239
- | Prop | Type | Default | Description |
240
- |------|------|---------|-------------|
241
- | `modelValue` | `number[]` | — | v-model binding (array for range) |
242
- | `min` | `number` | `0` | Minimum value |
243
- | `max` | `number` | `100` | Maximum value |
244
- | `step` | `number` | `1` | Step increment |
245
- | `disabled` | `boolean` | `false` | Disabled state |
246
- | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Slider direction |
247
- | `thumbs` | `number` | `1` | Number of thumbs |
248
- | `labels` | `string[]` | — | Labels for thumbs |
249
- | `label` | `string` | — | Overall label |
250
- | `displayValue` | `boolean` | `false` | Show current value |
251
- | `hideRange` | `boolean` | `false` | Hide the colored range track |
252
-
253
- **Slots**: `default` (custom thumb rendering)
254
- **CSS classes**: `slider`, `slider-track`, `slider-range`, `slider-thumb`
255
-
256
- ```html
257
- <!-- Basic slider -->
258
- <VuSlider v-model="volume" :min="0" :max="100" label="Volume" />
259
-
260
- <!-- Range slider (two thumbs) -->
261
- <VuSlider v-model="priceRange" :min="0" :max="1000" :thumbs="2" label="Price Range" />
262
- ```
263
-
264
- ### `<VuDatePicker>`
265
-
266
- Date picker with calendar popup.
267
-
268
- Composed from: `DatePickerBase`, `DatePickerInner`, `DatePickerPopup`.
269
-
270
- ```html
271
- <VuDatePicker v-model="date" label="Start Date" />
272
- ```
273
-
274
- Uses `@internationalized/date` for date handling.
275
-
276
- ## Common patterns
277
-
278
- ### Pattern: Form with validation
279
-
280
- ```html
281
- <form @submit.prevent="submit" class="flex flex-col gap-$m">
282
- <VuInput v-model="name" label="Name" required :error="errors.name" />
283
- <VuInput v-model="email" label="Email" required :error="errors.email" />
284
-
285
- <VuInput label="Role">
286
- <VuSelect v-model="role" :items="roles" />
287
- </VuInput>
288
-
289
- <VuCheckbox v-model="terms" label="I agree to the terms" :error="errors.terms" />
290
-
291
- <VuButton label="Submit" class="scope-primary c8-filled" type="submit" />
292
- </form>
293
- ```
294
-
295
- ### Pattern: Select inside Input wrapper
296
-
297
- `VuSelect` should be wrapped in `VuInput` for labels and design consistency:
298
-
299
- ```html
300
- <VuInput label="Country" design="filled" :error="countryError">
301
- <VuSelect v-model="country" :items="countries" />
302
- </VuInput>
303
- ```
304
-
305
- ### Pattern: Input with prefix/suffix slots
306
-
307
- ```html
308
- <VuInput v-model="price" label="Price" design="filled">
309
- <template #prepend>$</template>
310
- <template #append>.00</template>
311
- </VuInput>
312
- ```
313
-
314
- ### Pattern: Segmented input group
315
-
316
- Use the `segmented` class to visually join adjacent inputs:
317
-
318
- ```html
319
- <div class="segmented">
320
- <VuInput v-model="firstName" label="First Name" />
321
- <VuInput v-model="lastName" label="Last Name" />
322
- </div>
323
- ```
324
-
325
- ## Integration
326
-
327
- - Form components use `i8-*` shortcuts for styling. Override through `vunorShortcuts()` (see [shortcuts.md](shortcuts.md)).
328
- - Error states set `data-error` attribute and apply error color scoping automatically.
329
- - The `design` prop maps to `i8-flat`, `i8-filled`, or `i8-round` CSS classes.
330
- - Combobox combines Input and Select functionality — it renders an `i8` wrapper with a dropdown popup.
331
-
332
- ## Best practices
333
-
334
- - Wrap `VuSelect` inside `VuInput` for consistent label/error handling.
335
- - Use `design` prop consistently across a form — mixing designs looks inconsistent.
336
- - Pass error messages as strings (not just `true`) to display the error text below the input.
337
- - Use `autoGrow` on textarea inputs for better UX — it expands as the user types.
338
- - For multi-select Combobox, enable `checkboxItems` to make selection state obvious.
339
-
340
- ## Gotchas
341
-
342
- - `VuCheckbox` uses `data-state="checked"` / `"unchecked"` / `"indeterminate"` — not the `:checked` CSS pseudo-class.
343
- - `VuSelect` popup portals to `<body>`. Scope CSS selectors to `.select-content` to target it.
344
- - Reka UI renders hidden native `<select>/<option>` for form submission alongside the custom UI. `[role="option"]` matches both — use `div[role="option"]` within `.select-content` for the visible options.
345
- - `disabled` renders as `aria-disabled="true"`, not the HTML `disabled` attribute. Use `[aria-disabled="true"]` in CSS selectors.
346
- - `required` renders as `aria-required="true"`, not the HTML `required` attribute.
347
- - Input `type` prop accepts `'text'` or `'textarea'` — for HTML input types like `'password'` or `'email'`, pass them through the native attrs.
348
- - The `modelValue` for `VuSlider` is always an array, even for a single thumb.
@@ -1,223 +0,0 @@
1
- # Color palette — vunor
2
-
3
- > Oklab perceptual color system, seed colors, layers, surfaces, scopes, and the current-color system.
4
-
5
- ## Concepts
6
-
7
- Vunor generates its color system using `@prostojs/palitra`, built on the Oklab perceptual color model. Unlike RGB/HSL, Oklab grades colors by how people actually see them — `primary-500` and `error-500` sit at the same perceptual luminance regardless of hue.
8
-
9
- For each seed color, palitra generates:
10
- 1. **Main palette** (10 steps: 50, 100, 200, ..., 900) — luminance-graded with perceptually uniform steps
11
- 2. **Layer palette** (5 steps: 0–4) — desaturated variants for backgrounds
12
-
13
- All values are emitted as CSS custom properties.
14
-
15
- ### Color architecture
16
-
17
- ```
18
- seed color → palitra (Oklab) → main palette (50–900)
19
- → light layers (0–4)
20
- → dark layers (0–4)
21
-
22
- CSS custom properties
23
-
24
- scope-{color} → --scope-color-{step}
25
- current-bg-* → --current-bg
26
- bg-current → applies --current-bg
27
- layer-{0-4} → full bg+text+icon
28
- surface-{step} → full bg+text+icon
29
- ```
30
-
31
- ## Configuration
32
-
33
- ### Seed colors
34
-
35
- Pass to `presetVunor({ palette: { colors: { ... } } })`:
36
-
37
- | Name | Default | Purpose |
38
- |------|---------|---------|
39
- | `primary` | `#004eaf` | Main brand color |
40
- | `secondary` | `#edd812` | Accent / highlight |
41
- | `good` | `#7bc76a` | Success / positive |
42
- | `warn` | `#ef9421` | Warning |
43
- | `error` | `#bf5a5f` | Error / negative |
44
- | `grey` | `#858892` | Neutral greys |
45
- | `neutral` | `#5da0c5` | Alternative neutral |
46
-
47
- Each color can be a simple hex string or an object with fine-tuning:
48
-
49
- ```ts
50
- palette: {
51
- colors: {
52
- primary: '#6B4EFF', // simple swap
53
- error: {
54
- color: '#DC2626',
55
- vivid: { dark: 0.3, light: 0.3 }, // hue rotation at palette edges
56
- saturate: { dark: -0.1, light: -0.1 }, // desaturate slightly
57
- },
58
- },
59
- }
60
- ```
61
-
62
- ### Advanced palette controls
63
-
64
- ```ts
65
- palette: {
66
- lightest: 0.97, // max luminance for lightest step (0–1)
67
- darkest: 0.24, // min luminance for darkest step (0–1)
68
- layersDepth: 0.08, // brightness step between layer-0 and layer-4
69
-
70
- mainPalette: {
71
- luminance: { dark: 0.26, middle: 0.62, light: 0.97 },
72
- saturate: { dark: -0.25, light: -0.25 },
73
- vivid: { dark: 0.1, light: 0.2 },
74
- },
75
- layerPalette: {
76
- desaturate: 0.2,
77
- luminance: { dark: 0.24, light: 0.32 },
78
- },
79
- }
80
- ```
81
-
82
- ## API Reference
83
-
84
- ### Color scope classes
85
-
86
- #### `scope-{color}`
87
-
88
- Sets `--scope-color-*` CSS custom properties for an entire subtree.
89
-
90
- ```html
91
- <div class="scope-primary">
92
- <!-- All children can use scope-aware utilities -->
93
- <button class="c8-filled">Uses primary palette</button>
94
- </div>
95
- ```
96
-
97
- Valid color names: `primary`, `secondary`, `good`, `warn`, `error`, `grey`, `neutral`.
98
-
99
- #### `current-{target}-{color}-{step}`
100
-
101
- Sets a specific `--current-{target}` CSS variable to a specific palette step.
102
-
103
- ```html
104
- <div class="current-bg-primary-500 current-text-primary-50">
105
- <div class="bg-current text-current">Colored via CSS vars</div>
106
- </div>
107
- ```
108
-
109
- Targets: `text`, `bg`, `icon`, `border`, `outline`, `caret`, `hl` (highlight).
110
-
111
- #### `{target}-current` / `{target}-current/{opacity}`
112
-
113
- Applies the current-scoped color.
114
-
115
- ```html
116
- <div class="current-bg-error-500">
117
- <div class="bg-current">Full opacity error bg</div>
118
- <div class="bg-current/50">50% opacity error bg</div>
119
- </div>
120
- ```
121
-
122
- ### Layers
123
-
124
- #### `layer-{0-4}`
125
-
126
- Full background + text + icon styling that automatically adapts to light/dark mode.
127
-
128
- ```html
129
- <div class="layer-0 scope-primary">
130
- <!-- Light: lightest bg, dark text. Dark mode: darkest bg, light text -->
131
- <div class="layer-1">
132
- <!-- One step deeper -->
133
- <div class="layer-2">
134
- <!-- Even deeper -->
135
- </div>
136
- </div>
137
- </div>
138
- ```
139
-
140
- - `layer-0` is the outermost (lightest in light mode, darkest in dark)
141
- - Each layer steps progressively toward the opposite extreme
142
- - Apply `scope-{color}` to tint the layers with a palette
143
-
144
- ### Surfaces
145
-
146
- #### `surface-{step}`
147
-
148
- Applies a specific palette stop as background + text + icon.
149
-
150
- ```html
151
- <div class="scope-primary surface-100">Lightly tinted</div>
152
- <div class="scope-error surface-500">Bold error banner</div>
153
- ```
154
-
155
- - `surface-0` through `surface-4` map to the layer scale
156
- - `surface-50` through `surface-900` map to the main 10-step palette
157
- - Custom surfaces can be defined in `palette.surfaces`
158
-
159
- ## Common patterns
160
-
161
- ### Pattern: Scoped button colors
162
-
163
- Use `scope-{color}` to set the palette, then a `c8-*` class for the visual style.
164
-
165
- ```html
166
- <button class="scope-primary c8-filled">Primary</button>
167
- <button class="scope-error c8-outlined">Error</button>
168
- <button class="scope-good c8-light">Success</button>
169
- ```
170
-
171
- ### Pattern: Themed card sections
172
-
173
- Nest layers to create visual depth.
174
-
175
- ```html
176
- <div class="layer-0 scope-primary">
177
- <VuCard>
178
- <div class="layer-1">
179
- <p>Slightly deeper background</p>
180
- <div class="layer-2">
181
- <p>Even deeper</p>
182
- </div>
183
- </div>
184
- </VuCard>
185
- </div>
186
- ```
187
-
188
- ### Pattern: Custom current-color usage
189
-
190
- Set colors independently for different targets.
191
-
192
- ```html
193
- <div class="current-bg-primary-100 current-text-primary-900 current-border-primary-300">
194
- <div class="bg-current text-current border-current border-1">
195
- Custom color combination
196
- </div>
197
- </div>
198
- ```
199
-
200
- ### Pattern: Dark mode awareness
201
-
202
- Layers handle dark mode automatically. No need for explicit `dark:` prefixes on layer/surface classes.
203
-
204
- ```html
205
- <!-- This automatically adapts to dark mode -->
206
- <div class="layer-0 scope-primary">
207
- <div class="layer-1">Content</div>
208
- </div>
209
- ```
210
-
211
- ## Best practices
212
-
213
- - Use `scope-{color}` at the highest reasonable ancestor — children inherit the scope.
214
- - Prefer `layer-*` for page-level backgrounds and `surface-*` for specific component accents.
215
- - Don't hard-code palette step numbers unless you need a specific color — let layers and surfaces handle light/dark adaptation.
216
- - When customizing the palette, test both light and dark modes — Oklab ensures perceptual consistency, but extreme `vivid`/`saturate` overrides can break the balance.
217
-
218
- ## Gotchas
219
-
220
- - `scope-{color}` only sets CSS variables — it doesn't apply any visible styles by itself. You need `layer-*`, `surface-*`, `bg-current`, etc. to consume the scope.
221
- - Layer numbers don't correspond to z-index — they're visual depth levels only.
222
- - Swapping the primary color changes every component that uses `scope-primary` — this is by design but can be surprising if components mix scopes unexpectedly.
223
- - `current-bg-scope-color-500` (note: `scope-color`, not a specific palette name) references whatever the current scope is. This is used internally by `c8-filled` and similar shortcut patterns.