@tenphi/glaze 0.19.0 → 1.1.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/README.md +85 -31
- package/dist/index.cjs +391 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -35
- package/dist/index.d.mts +99 -35
- package/dist/index.mjs +388 -234
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +589 -413
- package/docs/methodology.md +125 -50
- package/docs/migration.md +81 -144
- package/docs/okhst.md +116 -254
- package/package.json +1 -1
package/docs/methodology.md
CHANGED
|
@@ -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
|
|
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
|
|
25
|
-
(`100 - t`) plus a scheme window, so relative tone
|
|
26
|
-
|
|
27
|
-
in
|
|
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
|
-
|
|
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
|
|
35
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
122
|
-
are enough. There is no
|
|
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
|
|
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`
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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
|
|
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
|
|
219
|
-
the page, and the label sits a muted
|
|
220
|
-
the
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
462
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
|
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
|
|
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.
|