@tenphi/glaze 0.15.0 → 0.16.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/docs/api.md CHANGED
@@ -115,14 +115,14 @@ Flat token map grouped by scheme variant.
115
115
 
116
116
  ```ts
117
117
  theme.tokens()
118
- // → { light: { surface: 'okhsl(...)' }, dark: { surface: 'okhsl(...)' } }
118
+ // → { light: { surface: 'oklch(...)' }, dark: { surface: 'oklch(...)' } }
119
119
  ```
120
120
 
121
121
  `GlazeJsonOptions`:
122
122
 
123
123
  | Option | Default | Description |
124
124
  |---|---|---|
125
- | `format` | `'okhsl'` | Output color format. One of `'okhsl' \| 'rgb' \| 'hsl' \| 'oklch'`. |
125
+ | `format` | `'oklch'` | Output color format. One of `'rgb' \| 'hsl' \| 'oklch'`. `'okhsl'` and `'okhst'` throw — use `tasty()` for those. |
126
126
  | `modes` | `{ dark: true, highContrast: false }` (or global config) | Which scheme variants to include. |
127
127
 
128
128
  ### `theme.tasty(options?)`
@@ -141,10 +141,12 @@ theme.tasty()
141
141
 
142
142
  | Option | Default | Description |
143
143
  |---|---|---|
144
- | `format` | `'okhsl'` | Output color format. |
144
+ | `format` | `'okhsl'` | Output color format. `'okhsl'` and `'okhst'` are supported here (Tasty-only spaces). |
145
145
  | `modes` | global config | Which scheme variants to include. |
146
146
  | `states.dark` | `'@dark'` (or global config) | State alias for dark mode tokens. |
147
147
  | `states.highContrast` | `'@high-contrast'` (or global config) | State alias for high-contrast tokens. |
148
+ | `splitHue` | `false` | Emit hue as a separate custom property (`#name-hue` token + `var()` in `oklch` values). Requires `format: 'oklch'` and every color to be pastel. |
149
+ | `name` | `'theme'` | Base name for the theme-level hue var (`#theme-hue` / `--theme-hue`). Palette export auto-derives this from the theme name. |
148
150
  | `prefix` | (palette only) | See [Palette](#palette). |
149
151
 
150
152
  When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@dark & @high-contrast'`).
@@ -156,8 +158,8 @@ Per-color JSON map.
156
158
  ```ts
157
159
  theme.json()
158
160
  // → {
159
- // surface: { light: 'okhsl(...)', dark: 'okhsl(...)' },
160
- // text: { light: 'okhsl(...)', dark: 'okhsl(...)' },
161
+ // surface: { light: 'oklch(...)', dark: 'oklch(...)' },
162
+ // text: { light: 'oklch(...)', dark: 'oklch(...)' },
161
163
  // }
162
164
  ```
163
165
 
@@ -181,11 +183,137 @@ theme.css();
181
183
 
182
184
  | Option | Default | Description |
183
185
  |---|---|---|
184
- | `format` | `'rgb'` | Output color format. |
186
+ | `format` | `'rgb'` | Output color format. `'okhsl'` and `'okhst'` throw — use `tasty()` for those. |
185
187
  | `suffix` | `'-color'` | Suffix appended to each CSS property name. Pass `''` for bare property names. |
188
+ | `splitHue` | `false` | Emit hue as a separate `--*-hue` custom property referenced via `var()` in `oklch` color values. Requires `format: 'oklch'` and every color to be pastel. Shadow/mix colors stay inline (blended hue; they do not follow `--hue` rotation). |
189
+ | `name` | `'theme'` | Base name for the theme-level hue var (`--theme-hue`). Palette export auto-derives this from the theme name. |
186
190
 
187
191
  `GlazeCssResult` always contains all four keys (`light`, `dark`, `lightContrast`, `darkContrast`); empty if no colors are defined for that variant.
188
192
 
193
+ ### `theme.dtcg(options?)`
194
+
195
+ W3C [Design Tokens Format Module (2025.10)](https://www.designtokens.org/) documents — the vendor-neutral JSON format consumed by Figma, Tokens Studio, Style Dictionary v4+, Terrazzo, Penpot, and every DTCG-compatible tool. Returns one spec-conformant token tree per scheme variant.
196
+
197
+ ```ts
198
+ theme.dtcg()
199
+ // → {
200
+ // light: {
201
+ // surface: {
202
+ // $type: 'color',
203
+ // $value: { colorSpace: 'srgb', components: [0.96, 0.94, 0.98], hex: '#f5f0fa' },
204
+ // },
205
+ // },
206
+ // dark: {
207
+ // surface: {
208
+ // $type: 'color',
209
+ // $value: { colorSpace: 'srgb', components: [0.16, 0.14, 0.2], hex: '#292333' },
210
+ // },
211
+ // },
212
+ // }
213
+ ```
214
+
215
+ Write each document to its own `.tokens.json` file — one file per scheme is the most tool-compatible convention (one per Style Dictionary theme / Tokens Studio set / Figma variable mode).
216
+
217
+ `GlazeDtcgOptions`:
218
+
219
+ | Option | Default | Description |
220
+ |---|---|---|
221
+ | `colorSpace` | `'srgb'` | Color space for `$value`. `'srgb'` emits gamma sRGB `components` (0–1) plus a `hex` hint — universally understood. `'oklch'` emits `[L, C, H]` components with no hex — Glaze-native, wide-gamut. |
222
+ | `modes` | global config | Which scheme variants to include. `light` is always present. |
223
+
224
+ `alpha` is included on `$value` only when the color's opacity is below 1. `$type` is always `'color'`.
225
+
226
+ ### `theme.dtcgResolver(options?)`
227
+
228
+ A single W3C [DTCG Resolver-Module](https://www.designtokens.org/) document describing **every scheme variant in one file** — an alternative to `dtcg()`'s per-scheme files for tools that resolve sets + modifiers (e.g. Dispersa). The light document becomes `sets.base.sources[0]` (the default context); each other variant becomes a context override on a single `scheme` modifier.
229
+
230
+ ```ts
231
+ theme.dtcgResolver({ modes: { highContrast: true } })
232
+ // → {
233
+ // version: '2025.10',
234
+ // sets: {
235
+ // base: {
236
+ // sources: [
237
+ // {
238
+ // surface: {
239
+ // $type: 'color',
240
+ // $value: { colorSpace: 'srgb', components: [0.96, 0.94, 0.98], hex: '#f5f0fa' },
241
+ // },
242
+ // },
243
+ // ],
244
+ // },
245
+ // },
246
+ // modifiers: {
247
+ // scheme: {
248
+ // default: 'light',
249
+ // contexts: {
250
+ // light: [],
251
+ // dark: [
252
+ // {
253
+ // surface: {
254
+ // $type: 'color',
255
+ // $value: { colorSpace: 'srgb', components: [0.16, 0.14, 0.2], hex: '#292333' },
256
+ // },
257
+ // },
258
+ // ],
259
+ // lightContrast: [ /* … */ ],
260
+ // darkContrast: [ /* … */ ],
261
+ // },
262
+ // },
263
+ // },
264
+ // resolutionOrder: [
265
+ // { $ref: '#/sets/base' },
266
+ // { $ref: '#/modifiers/scheme' },
267
+ // ],
268
+ // }
269
+ ```
270
+
271
+ **Why one modifier with four contexts.** Glaze resolves `darkContrast` independently — it is not `dark` + `lightContrast` layered. The resolver model composes modifiers additively (last in `resolutionOrder` wins on conflict), so two independent modifiers (`scheme` × `contrast`) would produce wrong values for the dark + high-contrast permutation. One `scheme` modifier with a context per variant keeps every resolved value exact. Choose `dtcgResolver()` when you want single-file theming and feed it to a resolver tool; choose `dtcg()` for maximum per-file tool compatibility.
272
+
273
+ `GlazeDtcgResolverOptions` (extends `GlazeDtcgOptions`, so `modes` and `colorSpace` pass through):
274
+
275
+ | Option | Default | Description |
276
+ |---|---|---|
277
+ | `colorSpace` | `'srgb'` | Same as `dtcg()` — flows through to every source and context. |
278
+ | `modes` | global config | Which scheme variants to emit as contexts. `light` is always present (the default); absent variants are omitted. |
279
+ | `setName` | `'base'` | Name of the single set holding the default (light) token tree. |
280
+ | `modifierName` | `'scheme'` | Name of the modifier describing the scheme axis. |
281
+ | `contextNames` | identity | Override the four context names (`light` / `dark` / `lightContrast` / `darkContrast`) — e.g. `{ dark: 'night' }`. |
282
+ | `version` | `'2025.10'` | Resolver document version. |
283
+
284
+ ### `theme.tailwind(options?)`
285
+
286
+ A Tailwind CSS v4 `@theme` block (light baseline) plus dark / high-contrast overrides under configurable selectors. Returns a single ready-to-paste CSS string. The `--color-*` namespace auto-generates `bg-*` / `text-*` / `border-*` utilities.
287
+
288
+ ```css
289
+ @theme {
290
+ --color-surface: oklch(0.96 0.01 280);
291
+ --color-text: oklch(0.3 0.05 280);
292
+ }
293
+ .dark {
294
+ --color-surface: oklch(0.16 0.01 280);
295
+ --color-text: oklch(0.85 0.05 280);
296
+ }
297
+ .high-contrast {
298
+ --color-surface: oklch(0.98 0.01 280);
299
+ --color-text: oklch(0.1 0.05 280);
300
+ }
301
+ .dark.high-contrast {
302
+ --color-surface: oklch(0.05 0.01 280);
303
+ --color-text: oklch(0.95 0.05 280);
304
+ }
305
+ ```
306
+
307
+ `GlazeTailwindOptions`:
308
+
309
+ | Option | Default | Description |
310
+ |---|---|---|
311
+ | `format` | `'oklch'` | Output color format for the values. |
312
+ | `namespace` | `'color-'` | CSS custom property namespace, forming `--<namespace><name>` (e.g. `--color-surface`). Named `namespace` to avoid clashing with the palette theme-prefix option. |
313
+ | `darkSelector` | `'.dark'` | Selector wrapping the dark overrides. Pass an at-rule like `'@media (prefers-color-scheme: dark)'` to drive dark mode from the OS preference (it nests `:root` automatically). |
314
+ | `highContrastSelector` | `'.high-contrast'` | Selector wrapping the light high-contrast overrides. The combined dark + high-contrast block uses `${darkSelector}${highContrastSelector}` (e.g. `.dark.high-contrast`). |
315
+ | `modes` | global config | Which scheme variants to include. The `@theme` block (light) is always emitted when colors exist. |
316
+
189
317
  ### `theme.export()`
190
318
 
191
319
  ```ts
@@ -219,6 +347,8 @@ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
219
347
  | `mode` | `'auto' \| 'fixed' \| 'static'` | Adaptation mode. Default: `'auto'`. See [Adaptation modes](#adaptation-modes). |
220
348
  | `flip` | `boolean` | Flip out-of-bounds results (relative `tone` overshoot / unmet `contrast`) to the opposite side instead of clamping. Default: the global `autoFlip` (`true`). See [`flip`](#flip). |
221
349
  | `opacity` | `number` | Fixed alpha 0–1. Output includes alpha in the CSS value. Combining with `contrast` is not recommended (a `console.warn` is emitted). |
350
+ | `pastel` | `boolean` | Per-color override for the hue-independent "safe" chroma limit used in OKHSL↔sRGB conversions (luminance, contrast solving, output formatting). Falls through to the global / per-theme `pastel` config when omitted. Default: unset. See [Per-color `pastel`](#per-color-pastel). |
351
+ | `role` | `RoleInput` | Semantic role against `base` (`'text'` / `'surface'` / `'border'` or an alias). Fixes APCA contrast polarity. Resolved via: explicit `role` → name inference → opposite of the base's role → `'text'`. See [Roles](#roles). |
222
352
  | `inherit` | `boolean` | Whether this color is inherited by child themes via `extend()`. Default: `true`. Set to `false` to make the color local to the current theme. |
223
353
 
224
354
  #### Tone values
@@ -276,8 +406,15 @@ contrast: { wcag: 6 } // WCAG 6
276
406
  contrast: { wcag: [4.5, 7] } // WCAG 4.5 normal / 7 high-contrast
277
407
  contrast: { apca: 60 } // APCA Lc 60
278
408
  contrast: { apca: [45, 60] } // APCA Lc 45 normal / 60 high-contrast
409
+ contrast: { apca: 'content' } // APCA preset -> Lc 60
410
+ contrast: { apca: ['content', 'body'] } // Lc 60 normal / 75 high-contrast
279
411
  ```
280
412
 
413
+ APCA preset keywords (Bronze Simple Mode conformance levels, role-independent):
414
+ `'preferred'` (Lc 90), `'body'` (75), `'content'` (60, ~AA), `'large'` (45, ~3:1),
415
+ `'non-text'` (30), `'min'` (15, point of invisibility). See
416
+ [`docs/okhst.md`](okhst.md) §APCA.
417
+
281
418
  The floor is applied independently per scheme — if the `tone` already satisfies it the tone is kept, otherwise the solver searches in tone (contrast-uniform → a closed-form WCAG seed and fast convergence) until the target is met.
282
419
 
283
420
  By default, the solver crosses to the opposite side of the base color when the requested tone direction cannot satisfy the floor. This is controlled per-color by [`flip`](#flip) (which defaults to the global `autoFlip`). Set `glaze.configure({ autoFlip: false })` — or `flip: false` on a single color — to keep strict directionality: unmet colors pin to that direction's 0 or 100 tone extreme instead of falling back to the original requested value.
@@ -299,6 +436,68 @@ theme.colors({
299
436
 
300
437
  Relative hue is always relative to the **theme seed hue**, not to a base color.
301
438
 
439
+ #### Per-color `pastel`
440
+
441
+ `pastel: true` on a single color def overrides the global / per-theme `pastel` config for that color only. It toggles the hue-independent "safe" chroma limit used in every OKHSL↔sRGB conversion that touches this color: luminance calculations during contrast solving, gamut clamping during sRGB blend / mix edges, and output formatting. The effective flag is carried on the resolved variant (`ResolvedColorVariant.pastel`) so formatting matches the gamut mapping applied during resolution.
442
+
443
+ ```ts
444
+ const theme = glaze(280, 80);
445
+ theme.colors({
446
+ plain: { tone: 50, saturation: 1 },
447
+ soft: { tone: 50, saturation: 1, pastel: true },
448
+ });
449
+ // theme.resolve().get('soft')!.light.pastel === true
450
+ // theme.css().light contains different rgb() triples for `--plain` and `--soft`
451
+ ```
452
+
453
+ Omit the field to inherit the global / per-theme `pastel` config — useful for keeping the default behavior while opting a single accent into the pastel gamut.
454
+
455
+ The flag is part of the def object, so `extend()` copies it through to child themes alongside the rest of the def. Override it again on the child to flip a single color back:
456
+
457
+ ```ts
458
+ const parent = glaze(280, 80);
459
+ parent.colors({ soft: { tone: 50, saturation: 1, pastel: true } });
460
+
461
+ const child = parent.extend({
462
+ colors: { soft: { tone: 50, saturation: 1, pastel: false } },
463
+ });
464
+ // child.resolve().get('soft')!.light.pastel === false
465
+ ```
466
+
467
+ > **Note:** Per-color `pastel` is also supported on `ShadowColorDef` and `MixColorDef` (see the tables above). For shadows the math itself happens in OKHSL space, so the flag mainly controls the gamut-mapped output formatting and any luminance verification for that variant.
468
+ >
469
+ > Standalone `glaze.color()` tokens accept the same `pastel` field on both the structured (`GlazeColorInput`) and value-shorthand (`GlazeColorOverrides`) forms, and it survives the `export()` / `glaze.colorFrom()` round-trip.
470
+
471
+ #### Roles
472
+
473
+ A color's `role` describes how it is used against its `base` and fixes **APCA contrast polarity** — which side is the foreground vs the background. APCA is asymmetric (`|apca(a,b)| ≠ |apca(b,a)|`), so the role picks the correct argument order; WCAG is symmetric and unaffected.
474
+
475
+ | Role | Polarity | Use | Aliases (name inference) |
476
+ |---|---|---|---|
477
+ | `'text'` | fg | Text / icons / foreground content | `text`, `fg`, `foreground`, `content`, `ink`, `label`, `stroke` |
478
+ | `'border'` | fg | Non-text spot elements (borders, dividers, outlines) | `border`, `divider`, `outline`, `separator`, `hairline`, `rule` |
479
+ | `'surface'` | bg | Backgrounds / fills | `surface`, `bg`, `background`, `fill`, `canvas`, `paper`, `layer` |
480
+
481
+ Resolution chain (per color):
482
+
483
+ 1. Explicit `role` (normalized from an alias) wins.
484
+ 2. Else, when `inferRole` is enabled (default), infer from the color name — the **last** recognized token wins (`button-text` → `text`, `input-bg` → `surface`, `card-outline` → `border`).
485
+ 3. Else, the opposite of the base's role (a `surface` base ⇒ this is `text`).
486
+ 4. Else, `'text'` (foreground) — i.e. the base is treated as the background.
487
+
488
+ ```ts
489
+ const theme = glaze(280, 60);
490
+ theme.colors({
491
+ surface: { tone: 90 },
492
+ text: { base: 'surface', contrast: { apca: 'content' } }, // inferred text
493
+ border: { base: 'surface', tone: '-10' }, // inferred border
494
+ });
495
+ // role fixes APCA polarity; set `pastel: true` explicitly if a border
496
+ // needs the hue-independent safe chroma limit.
497
+ ```
498
+
499
+ Disable name inference with `glaze.configure({ inferRole: false })` (the base-opposite and foreground-default fallbacks still apply).
500
+
302
501
  ### `ShadowColorDef`
303
502
 
304
503
  | Field | Type | Description |
@@ -308,6 +507,7 @@ Relative hue is always relative to the **theme seed hue**, not to a base color.
308
507
  | `fg` | `string` | Optional foreground color name for tinting and intensity modulation. Must reference a non-shadow color. Omit for an achromatic shadow at full user-specified intensity. |
309
508
  | `intensity` | `HCPair<number>` | Shadow intensity, 0–100. Supports HC pairs. |
310
509
  | `tuning` | `ShadowTuning` | Per-color tuning overrides. Merged field-by-field with the global `shadowTuning`. |
510
+ | `pastel` | `boolean` | Per-color `pastel` override. See [Per-color `pastel`](#per-color-pastel). |
311
511
  | `inherit` | `boolean` | Inheritance flag, default `true`. |
312
512
 
313
513
  See [Shadows](#shadows) below for the algorithm and tuning details.
@@ -323,6 +523,8 @@ See [Shadows](#shadows) below for the algorithm and tuning details.
323
523
  | `blend` | `'opaque' \| 'transparent'` | Default `'opaque'`. |
324
524
  | `space` | `'okhsl' \| 'srgb'` | Interpolation space for opaque blending. Default `'okhsl'`. Ignored for `'transparent'` (always composites in linear sRGB). |
325
525
  | `contrast` | `HCPair<ContrastSpec>` | Optional contrast floor against `base` (WCAG or APCA — see [`contrast`](#contrast-floor)). The solver adjusts the mix ratio (opaque) or opacity (transparent). |
526
+ | `pastel` | `boolean` | Per-color `pastel` override. See [Per-color `pastel`](#per-color-pastel). |
527
+ | `role` | `RoleInput` | Semantic role of the mixed result against `base`. Same semantics as `RegularColorDef.role` (see [Roles](#roles)). |
326
528
  | `inherit` | `boolean` | Inheritance flag, default `true`. |
327
529
 
328
530
  See [Mix colors](#mix-colors) below.
@@ -378,6 +580,8 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
378
580
  | `opacity` | `number` | Fixed alpha 0–1. |
379
581
  | `base` | `GlazeColorToken \| GlazeColorValue` | Optional dependency. See [Pairing colors](#pairing-colors). |
380
582
  | `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base` (WCAG or APCA). Without `base`, anchored to the literal seed. |
583
+ | `pastel` | `boolean` | Per-color `pastel` override. Falls through to the global / per-theme `pastel` config when omitted. See [Per-color `pastel`](#per-color-pastel). |
584
+ | `role` | `RoleInput` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
381
585
  | `name` | `string` | Debug label for warnings; doesn't change output keys. Reserved names (`'value'`, `'seed'`, `'externalBase'`) are rejected. |
382
586
 
383
587
  `GlazeFromInput` (from form) is `{ from: GlazeColorValue, ...colorOverrides }`:
@@ -394,6 +598,8 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
394
598
  | `contrast` | Contrast floor (WCAG or APCA). Without `base`, anchored to the literal seed; with `base`, solved per scheme. |
395
599
  | `base` | `GlazeColorToken` or raw `GlazeColorValue`. See [Pairing colors](#pairing-colors). |
396
600
  | `opacity` | Fixed alpha 0–1. Combining with `contrast` is not recommended — `console.warn` is emitted. |
601
+ | `pastel` | Per-color `pastel` override. Falls through to the global / per-theme `pastel` config when omitted. See [Per-color `pastel`](#per-color-pastel). |
602
+ | `role` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
397
603
  | `name` | Debug label only — surfaces in warnings/errors. Does not change output keys. |
398
604
 
399
605
  Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
@@ -434,9 +640,12 @@ A `GlazeColorToken` exposes:
434
640
  |---|---|
435
641
  | `token.resolve()` | Resolve as a `ResolvedColor` (light/dark/lightContrast/darkContrast variants). |
436
642
  | `token.token(options?)` | Flat token map (no color-name key). Options: `format`, `modes`, `states`. |
437
- | `token.tasty(options?)` | Tasty state map (no color-name key). Same options as `token.token`. |
643
+ | `token.tasty(options?)` | [Tasty](https://tasty.style) state map (no color-name key). Same options as `token.token`. |
438
644
  | `token.json(options?)` | JSON map (no color-name key). Options: `format`, `modes`. |
439
645
  | `token.css({ name, format?, suffix? })` | CSS custom property declarations grouped by scheme variant. `name` is **required** and becomes the variable identifier (`'brand'` → `--brand-color`). Defaults: `format: 'rgb'`, `suffix: '-color'` (matches `theme.css`). |
646
+ | `token.dtcg(options?)` | DTCG color tokens, one per scheme variant (no color-name key). Each entry is a full `{ $type: 'color', $value }` token. Options: `colorSpace` (`'srgb'` \| `'oklch'`), `modes`. |
647
+ | `token.dtcgResolver({ name, ... })` | A single DTCG Resolver-Module document for this color, keyed by `name` across all scheme variants. `name` is **required**. Same options as `theme.dtcgResolver()` plus `name`. |
648
+ | `token.tailwind({ name, ... })` | Tailwind v4 `@theme` block + dark / high-contrast overrides for this color. `name` is **required** (forms `--color-<name>`). Same options as `theme.tailwind()` plus `name`. |
440
649
  | `token.export()` | JSON-safe snapshot — pass to `glaze.colorFrom(...)` to rehydrate. |
441
650
 
442
651
  ### Per-instance config override
@@ -885,6 +1094,56 @@ const stylesheet = `
885
1094
  `palette.css()` accepts the same `GlazeCssOptions` as `theme.css()` plus `GlazePaletteExportOptions`.
886
1095
  It does not accept `modes`; all four result fields are always returned.
887
1096
 
1097
+ ### `palette.dtcg()`
1098
+
1099
+ DTCG export for a palette. Prefix defaults to `true` and the palette-level `primary` is honored (the primary theme's tokens are duplicated without prefix as aliases).
1100
+
1101
+ ```ts
1102
+ palette.dtcg()
1103
+ // → {
1104
+ // light: {
1105
+ // 'primary-surface': { $type: 'color', $value: { ... } },
1106
+ // 'surface': { $type: 'color', $value: { ... } }, // unprefixed alias
1107
+ // 'danger-surface': { $type: 'color', $value: { ... } },
1108
+ // },
1109
+ // dark: { ... },
1110
+ // }
1111
+ ```
1112
+
1113
+ Accepts `GlazeDtcgOptions` plus `GlazePaletteExportOptions`.
1114
+
1115
+ ### `palette.dtcgResolver()`
1116
+
1117
+ Resolver-Module export for a palette. Same as `theme.dtcgResolver()` but merges every theme (with prefix / `primary` aliasing) into the single `sets.base` source and each `scheme` context. Prefix defaults to `true`; the palette-level `primary` is honored.
1118
+
1119
+ ```ts
1120
+ palette.dtcgResolver()
1121
+ // → {
1122
+ // version: '2025.10',
1123
+ // sets: { base: { sources: [ { 'primary-surface': {…}, 'surface': {…}, 'danger-surface': {…} } ] } },
1124
+ // modifiers: { scheme: { default: 'light', contexts: { light: [], dark: [ {…} ] } } },
1125
+ // resolutionOrder: [ { $ref: '#/sets/base' }, { $ref: '#/modifiers/scheme' } ],
1126
+ // }
1127
+ ```
1128
+
1129
+ Accepts `GlazeDtcgResolverOptions` plus `GlazePaletteExportOptions`.
1130
+
1131
+ ### `palette.tailwind()`
1132
+
1133
+ Tailwind export for a palette. All themes are merged into a single `@theme` block (plus dark / high-contrast overrides), so each color is reachable as a Tailwind utility. Prefix defaults to `true`.
1134
+
1135
+ ```ts
1136
+ const css = palette.tailwind();
1137
+ // @theme {
1138
+ // --color-primary-surface: oklch(...);
1139
+ // --color-surface: oklch(...); /* unprefixed alias */
1140
+ // --color-danger-surface: oklch(...);
1141
+ // }
1142
+ // .dark { ... }
1143
+ ```
1144
+
1145
+ Accepts `GlazeTailwindOptions` plus `GlazePaletteExportOptions`. The palette `prefix` option (theme prefixing) is separate from `GlazeTailwindOptions.namespace` (the `--color-*` CSS namespace).
1146
+
888
1147
  ---
889
1148
 
890
1149
  ## Output formats
@@ -893,21 +1152,40 @@ Control the color format with the `format` option on any export method:
893
1152
 
894
1153
  | Format | Output (alpha = 1) | Output (alpha < 1) | Notes |
895
1154
  |---|---|---|---|
896
- | `'okhsl'` (default for tokens/tasty/json) | `okhsl(H S% L%)` | `okhsl(H S% L% / A)` | Glaze's native format, not a CSS function. |
897
- | `'rgb'` (default for css) | `rgb(R G B)` | `rgb(R G B / A)` | Rounded integers, modern space syntax. |
1155
+ | `'okhsl'` (default for `tasty()`) | `okhsl(H S% L%)` | `okhsl(H S% L% / A)` | Glaze's native format, not a CSS function. **Tasty-only** (`tasty()`, `token()`, `.tasty()`). |
1156
+ | `'okhst'` | `okhst(H S% T%)` | `okhst(H S% T% / A)` | OKHST tone axis. **Tasty-only** same restriction as `okhsl`. |
1157
+ | `'oklch'` (default for `tokens()` / `json()`) | `oklch(L C H)` | `oklch(L C H / A)` | OKLab-based LCH. Native CSS. Required for `splitHue`. |
1158
+ | `'rgb'` (default for `css()`) | `rgb(R G B)` | `rgb(R G B / A)` | Rounded integers, modern space syntax. |
898
1159
  | `'hsl'` | `hsl(H S% L%)` | `hsl(H S% L% / A)` | Modern space syntax. |
899
- | `'oklch'` | `oklch(L C H)` | `oklch(L C H / A)` | OKLab-based LCH. |
900
1160
 
901
1161
  ```ts
902
- theme.tokens(); // 'okhsl(280 60% 97%)'
1162
+ theme.tokens(); // 'oklch(0.965 0.0123 280)' (default)
903
1163
  theme.tokens({ format: 'rgb' }); // 'rgb(244 240 250)'
904
- theme.tokens({ format: 'hsl' }); // 'hsl(270.5 45.2% 95.8%)'
905
- theme.tokens({ format: 'oklch' }); // 'oklch(0.965 0.0123 280)'
1164
+ theme.tasty(); // 'okhsl(280 60% 97%)' (default)
1165
+ theme.tasty({ format: 'okhst' }); // 'okhst(280 60% 97%)'
906
1166
  ```
907
1167
 
908
1168
  All numeric output strips trailing zeros for cleaner CSS (e.g. `95` not `95.0`).
909
1169
 
910
- The `format` option works on every export: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, the same on `palette`, and on `token.token()` / `.tasty()` / `.json()` / `.css()`.
1170
+ The `format` option works on CSS-string exports: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `theme.tailwind()`, the same on `palette`, and on `token.token()` / `.tasty()` / `.json()` / `.css()` / `.tailwind()`. **`okhsl` and `okhst` throw on non-Tasty exports** (`tokens`, `json`, `css`, `tailwind`) — they are not native CSS color spaces.
1171
+
1172
+ ### Hue channel splitting (`splitHue`)
1173
+
1174
+ On `theme.css()`, `theme.tasty()`, `palette.css()`, `palette.tasty()`, and standalone `color.css()` with `format: 'oklch'`, set `splitHue: true` to emit hue as its own custom property so consumers can re-skin at runtime:
1175
+
1176
+ ```css
1177
+ /* theme.css({ format: 'oklch', splitHue: true, name: 'brand' }) */
1178
+ --brand-hue: 240;
1179
+ --accent-hue: calc(var(--brand-hue) + 20);
1180
+ --surface-color: oklch(0.52 0.06 var(--brand-hue));
1181
+ --accent-color: oklch(0.62 0.03 var(--accent-hue));
1182
+ ```
1183
+
1184
+ **Requirements:** every exported color must be pastel (`pastel: true` globally or per-color). Pastel mode bounds chroma by the hue-independent safe chroma at each lightness, so emitted `C` stays in sRGB for any rotated hue. Non-pastel palettes throw rather than emit values that would clip under rotation.
1185
+
1186
+ **Limitations:** `oklch` only (native CSS `var()` in the hue slot). Shadow and mix colors stay inline (blended hue). Standalone `.token()` / `.tasty()` do not support `splitHue` (return shape cannot carry the `#name-hue` declaration).
1187
+
1188
+ `theme.dtcg()` / `theme.dtcgResolver()` / `palette.dtcg()` / `palette.dtcgResolver()` ignore `format` — DTCG emits structured `$value` objects, not CSS strings. Use the `colorSpace` option (`'srgb'` or `'oklch'`) to pick the color representation instead.
911
1189
 
912
1190
  ---
913
1191
 
@@ -1027,6 +1305,8 @@ A `ToneWindow` is `[lo, hi]` (OKHSL-lightness endpoints, reference eps — the c
1027
1305
  | `modes.highContrast` | `false` | Include HC variants. |
1028
1306
  | `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
1029
1307
  | `autoFlip` | `true` | Default for each color's `flip`. When solving `contrast` (or applying a relative `tone` that overshoots `[0, 100]`), allow crossing to the opposite side instead of clamping. With `false`, only the requested direction is considered; unmet contrasts pin the tone to that direction's extreme (and emit a warning) and overshooting offsets clamp to the boundary. Override per color via [`flip`](#flip). |
1308
+ | `pastel` | `false` | Hue-independent "safe" chroma limit across all colors so scaling saturation never exceeds the sRGB boundary at any hue for the given lightness. Override per color via [`pastel`](#per-color-pastel). |
1309
+ | `inferRole` | `true` | Infer each color's [`role`](#roles) from its name when no explicit `role` is set. Set to `false` to opt out of name-based inference (the base-opposite and foreground-default fallbacks still apply). |
1030
1310
 
1031
1311
  | Method | Description |
1032
1312
  |---|---|