@tenphi/glaze 0.18.0 → 1.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.
@@ -4,8 +4,9 @@ A practical way to design a Glaze palette without fighting dark mode. Start from
4
4
  tone relationships, add contrast only where a role needs a readable floor, and
5
5
  let `extend()` carry the same decisions across status hues.
6
6
 
7
- For the full API surface, see [api.md](api.md). For the color model details, see
8
- [okhst.md](okhst.md).
7
+ For the full API surface, see [api.md](api.md). For the Glaze color-model
8
+ overview, see [okhst.md](okhst.md); the derivation lives in the
9
+ [canonical OKHST specification](https://github.com/tenphi/okhst).
9
10
 
10
11
  ## The mental model
11
12
 
@@ -21,24 +22,53 @@ themes:
21
22
  ladders, shadows, code colors, overlays, and anything that should not be
22
23
  repeated for every status hue.
23
24
 
24
- The main simplification is OKHST tone. Dark mode is now a single inversion
25
- (`100 - t`) plus a scheme window, so relative tone shifts stay consistent between
26
- light and dark. A token that is `-4` tone from its base is the same kind of step
27
- in both schemes.
25
+ The main simplification is OKHST tone. Dark mode is a single inversion
26
+ (`100 - t`) plus a scheme tone window, so a relative **tone delta** stays
27
+ anchored to its base in every scheme. A token authored as `tone: '-4'` remains
28
+ the same kind of visual step in light and dark. The step is exactly
29
+ contrast-even for neutrals and approximate for chromatic colors; add a
30
+ `contrast` floor when the measured result matters.
28
31
 
29
- ## Authoring Rules
32
+ Use `glaze.color()` instead of a theme when you need one standalone color or
33
+ one base/dependent pair and do not need a named palette. The same tone,
34
+ adaptation, and contrast rules apply; see
35
+ [Standalone color tokens](api.md#standalone-color-tokens).
36
+
37
+ ## Implementation workflow
38
+
39
+ Build in dependency order so every decision has a clear base:
40
+
41
+ 1. Configure the output schemes and application states once.
42
+ 2. Choose the default theme's hue and saturation seed.
43
+ 3. Define root surfaces with absolute tones.
44
+ 4. Add dependent surfaces, text, borders, and icons with tone deltas.
45
+ 5. Add contrast floors only to roles that need measured readability or
46
+ recognizability.
47
+ 6. Choose adaptation per color: `auto`, `fixed`, or `static`.
48
+ 7. Add explicit HC pairs where high contrast should increase separation.
49
+ 8. Mark default-only tokens `inherit: false`, then extend the shared
50
+ definitions into status themes.
51
+ 9. Compose and export the palette in the shape your application consumes.
52
+ 10. Verify complete screens in all emitted scheme variants.
53
+
54
+ The sections below follow this order and build one palette incrementally.
55
+
56
+ ## Authoring decisions
30
57
 
31
58
  Use this order when defining a token:
32
59
 
33
60
  1. Pick the base it visually belongs to.
34
- 2. Use `tone` for visual distance: surface ladders, soft chips, disabled states,
35
- hover ramps, and other low-stakes relationships.
61
+ 2. Use an absolute numeric `tone` for independent placement. Use a signed
62
+ **tone delta** (`'+N'` / `'-N'`) for distance from a base: surface ladders,
63
+ soft chips, disabled states, hover ramps, and similar relationships.
36
64
  3. Add `contrast` only when readability or recognizability needs a measured
37
65
  floor.
38
- 4. Use APCA presets for content-like colors:
66
+ 4. Prefer APCA presets for content-like colors when perceptual readability is
67
+ the design goal:
39
68
  `contrast: { apca: 'content' }` or
40
69
  `contrast: { apca: ['content', 'body'] }`.
41
- 5. Use WCAG numbers or presets when you specifically need a ratio target:
70
+ 5. Use WCAG numbers or presets when compatibility, policy, or migration
71
+ requires a WCAG ratio:
42
72
  `contrast: 4.5`, `contrast: 'AAA'`, or `contrast: { wcag: [4.5, 7] }`.
43
73
  6. Let token names infer APCA roles. Names ending in `text`, `label`, `border`,
44
74
  `surface`, `fill`, `bg`, and similar aliases already tell Glaze which side is
@@ -46,16 +76,35 @@ Use this order when defining a token:
46
76
  7. Add high-contrast pairs only where HC should intentionally tighten:
47
77
  text/content contrast, border tone, shadow intensity, mix value, or similar.
48
78
 
49
- `mode: 'auto'` is the default and should be the default choice. Use
50
- `mode: 'fixed'` for brand fills or inverse surfaces that must stay recognizable
51
- instead of tone-inverting. Use `mode: 'static'` only when the exact authored tone
52
- must render in every scheme.
79
+ ### Choosing adaptation mode
53
80
 
54
- ## Seed and Configure
81
+ | Mode | Choose it when |
82
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
83
+ | `auto` (default) | The color should exchange light/dark positions through dark tone inversion. Typical for surfaces, text, borders, and icons. |
84
+ | `fixed` | A brand fill, status banner, or inverse surface should stay on the authored side of the tone scale. |
85
+ | `static` | The exact authored tone and saturation must render in every scheme, without tone-window mapping or dark desaturation. |
86
+
87
+ Dark tone inversion is controlled by `mode`; it is unrelated to `autoFlip`.
88
+ `autoFlip` only allows an overshooting tone delta or an unsuccessful contrast
89
+ direction to reverse around its base.
90
+
91
+ ### Root or dependent?
92
+
93
+ - Use a root color when its tone has meaning on its own: the page surface, a
94
+ fixed brand anchor, or a scheme extreme.
95
+ - Use a dependent color when its purpose exists relative to another token:
96
+ text on a surface, a border around a fill, or a tint of an accent.
97
+ - Setting `base` with an absolute tone is valid when the color needs a contrast
98
+ relationship but not a tone delta. The absolute position is resolved
99
+ independently; `contrast` acts as a safety floor.
100
+
101
+ ## Seed and configure
55
102
 
56
103
  Keep hue decisions named and configure output modes once:
57
104
 
58
105
  ```ts
106
+ import { glaze } from '@tenphi/glaze';
107
+
59
108
  const PURPLE_HUE = 280.3;
60
109
  const SUCCESS_HUE = 156.9;
61
110
  const DANGER_HUE = 23.1;
@@ -93,7 +142,7 @@ These names are not only readable. They also help APCA role inference pick the
93
142
  right polarity. For example, `button-text` is foreground, `input-bg` is a
94
143
  surface, and `card-outline` is a border.
95
144
 
96
- ## Build the Default Theme
145
+ ## Build the default theme
97
146
 
98
147
  Start with the surface family. It is mostly tone, with small saturation changes
99
148
  to keep the ladder visually coherent:
@@ -118,10 +167,11 @@ defaultTheme.colors({
118
167
  });
119
168
  ```
120
169
 
121
- Because tone shifts are consistent across schemes, these small relative offsets
122
- are enough. There is no separate dark-mode curve to tune.
170
+ Because each tone delta re-anchors to the resolved surface in every scheme,
171
+ these small relative offsets are enough to define the ladder. There is no
172
+ separate dark-mode curve to tune.
123
173
 
124
- ### Text and Borders
174
+ ### Text and borders
125
175
 
126
176
  Use a hard edge tone for maximum-prominence text, and APCA floors for softer
127
177
  content:
@@ -156,15 +206,16 @@ defaultTheme.colors({
156
206
  });
157
207
  ```
158
208
 
159
- `surface-text` does not need a contrast floor because it is intentionally pinned
160
- near the edge. The soft variants use `tone: '-1'` as the direction and APCA as
161
- the readable floor. `border` uses an HC tone pair because borders usually need a
162
- larger visible step in high contrast.
209
+ `surface-text` uses an absolute `tone: 2` despite having a base: it is
210
+ intentionally edge-anchored, and the base records the relationship used by
211
+ role inference. The soft variants use a `-1` tone delta as the preferred
212
+ direction and APCA as the readable floor. `border` uses an HC tone-delta pair
213
+ because borders usually need a larger visible step in high contrast.
163
214
 
164
215
  Repeat the same pattern for `surface-2` and `surface-3` only if components need
165
216
  text directly on those surfaces.
166
217
 
167
- ### Neutral Utility Tokens
218
+ ### Neutral utility tokens
168
219
 
169
220
  Keep neutral-only primitives local to the default theme:
170
221
 
@@ -193,7 +244,7 @@ defaultTheme.colors({
193
244
  Absolute tones are fine for primitives whose job is visual placement rather than
194
245
  a strict relationship to a specific surface.
195
246
 
196
- ## Chips and Disabled States
247
+ ## Chips and disabled states
197
248
 
198
249
  For subtle fills, tone is usually clearer than contrast:
199
250
 
@@ -215,9 +266,9 @@ defaultTheme.colors({
215
266
  });
216
267
  ```
217
268
 
218
- This says exactly what the pair should do: the chip sits a few tone steps off
219
- the page, and the label sits a muted distance from the chip. `autoFlip: false` keeps
220
- the relative label offset on the authored side when it reaches the edge.
269
+ This says exactly what the pair should do: the chip sits a small tone delta off
270
+ the page, and the label sits a muted delta from the chip. `autoFlip: false`
271
+ keeps the label on the authored side when the delta reaches the edge.
221
272
 
222
273
  Use contrast instead when the chip must hit an explicit accessibility floor:
223
274
 
@@ -237,7 +288,7 @@ When a token needs the scheme extreme, use `tone: 'min'` or `tone: 'max'`
237
288
  directly. Avoid large magic numbers or fake contrast floors just to push a color
238
289
  to the edge.
239
290
 
240
- ## Fixed Surfaces and Accent Fills
291
+ ## Fixed surfaces and accent fills
241
292
 
242
293
  Use `mode: 'fixed'` when the authored color should stay recognizable across
243
294
  schemes.
@@ -280,7 +331,7 @@ The accent fill family is a fixed chain against a fixed text anchor. The names
280
331
  infer `surface` and `text` roles, so APCA gets the right polarity without extra
281
332
  fields.
282
333
 
283
- ## Adaptive Accent Foregrounds
334
+ ## Adaptive accent foregrounds
284
335
 
285
336
  Brand foregrounds that sit on neutral surfaces should stay adaptive:
286
337
 
@@ -333,7 +384,7 @@ These are inherited, so status themes automatically get
333
384
  `success-accent-disabled-surface`, `danger-accent-disabled-surface`, and the
334
385
  matching text tokens.
335
386
 
336
- ## Special Purpose Colors
387
+ ## Special-purpose colors
337
388
 
338
389
  Use absolute `hue` overrides for tokens that should come from another hue family
339
390
  but keep the same adaptation behavior:
@@ -458,10 +509,12 @@ defaultTheme.colors({
458
509
  ```
459
510
 
460
511
  Transparent mixes are good for hover overlays. Opaque mixes are good for solid
461
- tints. Mix colors can also use `contrast`; the solver adjusts the value or
462
- opacity to hit the floor.
512
+ tints. Opaque mixes default to perceptual OKHSL interpolation; choose `srgb`
513
+ when matching channel compositing matters. Transparent mixes always composite
514
+ in linear sRGB. Mix colors can also use `contrast`; the solver adjusts the
515
+ value or opacity to hit the floor. See [Mix colors](api.md#mix-colors).
463
516
 
464
- ## Extend into Status Themes
517
+ ## Extend into status themes
465
518
 
466
519
  Once the default theme is shaped, create colored siblings by replacing hue and
467
520
  overriding only the root surface that should become visibly tinted:
@@ -495,7 +548,7 @@ const noteTheme = defaultTheme.extend({
495
548
  The inherited accent and disabled tokens now resolve in each status hue. Tokens
496
549
  marked `inherit: false` stay default-only, so sibling themes remain small.
497
550
 
498
- ## Export the Palette
551
+ ## Export the palette
499
552
 
500
553
  Compose the themes once:
501
554
 
@@ -513,22 +566,38 @@ const palette = glaze.palette({
513
566
  The usual export shape is default unprefixed and status themes prefixed:
514
567
 
515
568
  ```ts
516
- palette.tasty({
517
- prefix: {
518
- default: '',
519
- primary: 'primary-',
520
- success: 'success-',
521
- danger: 'danger-',
522
- warning: 'warning-',
523
- note: 'note-',
524
- },
525
- });
569
+ const prefix = {
570
+ default: '',
571
+ primary: 'primary-',
572
+ success: 'success-',
573
+ danger: 'danger-',
574
+ warning: 'warning-',
575
+ note: 'note-',
576
+ };
577
+
578
+ palette.tasty({ prefix });
579
+ ```
580
+
581
+ An explicit prefix map is the clearest choice when the palette has a neutral
582
+ `default` theme. The separate palette `primary` option serves another pattern:
583
+ it duplicates one named theme without a prefix while retaining its prefixed
584
+ tokens. Do not combine the two accidentally; choose the token namespace your
585
+ components expect.
586
+
587
+ The palette design is independent of the exporter:
588
+
589
+ ```ts
590
+ palette.tokens({ prefix }); // JavaScript maps, native oklch by default
591
+ palette.css({ prefix }); // CSS custom-property declarations
592
+ palette.dtcg({ prefix }); // one design-token tree per scheme
593
+ palette.tailwind({ prefix }); // Tailwind CSS v4 theme
526
594
  ```
527
595
 
528
- See [migration.md](migration.md) for export shapes, prefix maps, Tasty wiring,
529
- CSS variables, and JSON integration.
596
+ Use `palette.tasty({ prefix })` for [Tasty](https://tasty.style) state bindings. See
597
+ [migration.md](migration.md#choosing-an-export) for output shapes, application
598
+ wiring, and the `primary` alias pattern.
530
599
 
531
- ## High Contrast
600
+ ## High contrast
532
601
 
533
602
  High contrast is not a separate palette. Any value that accepts an HC pair can
534
603
  tighten the HC variant: `tone`, `contrast`, shadow `intensity`, and mix `value`.
@@ -554,8 +623,14 @@ Before shipping a palette, verify:
554
623
  - Accent fills use `mode: 'fixed'`; accent foregrounds on neutral UI stay
555
624
  `mode: 'auto'` and are based on `surface`.
556
625
  - Ambiguous APCA tokens have an explicit `role`; obvious names rely on inference.
557
- - Low-stakes visual relationships use relative `tone` instead of fake contrast
626
+ - Low-stakes visual relationships use tone deltas instead of fake contrast
558
627
  floors.
559
628
  - `inherit: false` is set on default-only tokens so status themes stay focused.
560
629
  - HC pairs exist where high contrast should visibly tighten.
561
630
  - `glaze.configure({ states, modes })` matches the states registered in the app.
631
+ - Every emitted scheme (`light`, `dark`, `lightContrast`, `darkContrast`) has
632
+ been reviewed on complete screens, not only in a token grid.
633
+ - Rendered WCAG/APCA results have been checked for chromatic foreground/base
634
+ pairs that carry accessibility requirements.
635
+ - Resolution emits no unexplained unreachable-contrast or token-collision
636
+ warnings.