@tenphi/glaze 0.0.0-snapshot.99c649d → 0.0.0-snapshot.a30ab54
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 +208 -22
- package/dist/index.cjs +302 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -22
- package/dist/index.d.mts +116 -22
- package/dist/index.mjs +301 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Glaze generates robust **light**, **dark**, and **high-contrast** color schemes
|
|
|
22
22
|
|
|
23
23
|
- **OKHSL color space** — perceptually uniform hue and saturation
|
|
24
24
|
- **WCAG 2 contrast solving** — automatic lightness adjustment to meet AA/AAA targets
|
|
25
|
+
- **Mix colors** — blend two colors with OKHSL or sRGB interpolation, opaque or transparent, with optional contrast solving
|
|
25
26
|
- **Shadow colors** — OKHSL-native shadow computation with automatic alpha, fg/bg tinting, and per-scheme adaptation
|
|
26
27
|
- **Light + Dark + High-Contrast** — all schemes from one definition
|
|
27
28
|
- **Per-color hue override** — absolute or relative hue shifts within a theme
|
|
@@ -70,8 +71,8 @@ const success = primary.extend({ hue: 157 });
|
|
|
70
71
|
|
|
71
72
|
// Compose into a palette and export
|
|
72
73
|
const palette = glaze.palette({ primary, danger, success });
|
|
73
|
-
const tokens = palette.tokens({
|
|
74
|
-
// → { light: { 'primary-surface': 'okhsl(...)',
|
|
74
|
+
const tokens = palette.tokens({ primary: 'primary' });
|
|
75
|
+
// → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
## Core Concepts
|
|
@@ -413,6 +414,139 @@ const css = glaze.format(v, 'oklch');
|
|
|
413
414
|
}
|
|
414
415
|
```
|
|
415
416
|
|
|
417
|
+
## Mix Colors
|
|
418
|
+
|
|
419
|
+
Mix colors blend two existing colors together. Use them for hover overlays, tints, shades, and any derived color that sits between two reference colors.
|
|
420
|
+
|
|
421
|
+
### Opaque Mix
|
|
422
|
+
|
|
423
|
+
Produces a solid color by interpolating between `base` and `target`:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
theme.colors({
|
|
427
|
+
surface: { lightness: 95 },
|
|
428
|
+
accent: { lightness: 30 },
|
|
429
|
+
|
|
430
|
+
// 30% of the way from surface toward accent
|
|
431
|
+
tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- `value` — mix ratio 0–100 (0 = pure base, 100 = pure target)
|
|
436
|
+
- The result is a fully opaque color (alpha = 1)
|
|
437
|
+
- Adapts to light/dark/HC schemes automatically via the resolved base and target
|
|
438
|
+
|
|
439
|
+
### Transparent Mix
|
|
440
|
+
|
|
441
|
+
Produces the target color with a controlled opacity — useful for hover overlays:
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
theme.colors({
|
|
445
|
+
surface: { lightness: 95 },
|
|
446
|
+
black: { lightness: 0, saturation: 0 },
|
|
447
|
+
|
|
448
|
+
hover: {
|
|
449
|
+
type: 'mix',
|
|
450
|
+
base: 'surface',
|
|
451
|
+
target: 'black',
|
|
452
|
+
value: 8,
|
|
453
|
+
blend: 'transparent',
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
// hover → target color (black) with alpha = 0.08
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The output color has `h`, `s`, `l` from the target and `alpha = value / 100`.
|
|
460
|
+
|
|
461
|
+
### Blend Space
|
|
462
|
+
|
|
463
|
+
By default, opaque mixing interpolates in OKHSL (perceptually uniform, consistent with Glaze's model). Use `space: 'srgb'` for linear sRGB interpolation, which matches browser compositing:
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
theme.colors({
|
|
467
|
+
surface: { lightness: 95 },
|
|
468
|
+
accent: { lightness: 30 },
|
|
469
|
+
|
|
470
|
+
// sRGB blend — matches what the browser would render
|
|
471
|
+
hover: { type: 'mix', base: 'surface', target: 'accent', value: 20, space: 'srgb' },
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
| Space | Behavior | Best for |
|
|
476
|
+
|---|---|---|
|
|
477
|
+
| `'okhsl'` (default) | Perceptually uniform OKHSL interpolation | Design token derivation |
|
|
478
|
+
| `'srgb'` | Linear sRGB channel interpolation | Matching browser compositing |
|
|
479
|
+
|
|
480
|
+
The `space` option only affects opaque blending. Transparent blending always composites in linear sRGB (matching browser alpha compositing).
|
|
481
|
+
|
|
482
|
+
### Contrast Solving
|
|
483
|
+
|
|
484
|
+
Mix colors support the same `contrast` prop as regular colors. The solver adjusts the mix ratio (opaque) or opacity (transparent) to meet the WCAG target:
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
theme.colors({
|
|
488
|
+
surface: { lightness: 95 },
|
|
489
|
+
accent: { lightness: 30 },
|
|
490
|
+
|
|
491
|
+
// Ensure the mixed color has at least AA contrast against surface
|
|
492
|
+
tint: {
|
|
493
|
+
type: 'mix',
|
|
494
|
+
base: 'surface',
|
|
495
|
+
target: 'accent',
|
|
496
|
+
value: 10,
|
|
497
|
+
contrast: 'AA',
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
// Ensure the transparent overlay has at least 3:1 contrast
|
|
501
|
+
overlay: {
|
|
502
|
+
type: 'mix',
|
|
503
|
+
base: 'surface',
|
|
504
|
+
target: 'accent',
|
|
505
|
+
value: 5,
|
|
506
|
+
blend: 'transparent',
|
|
507
|
+
contrast: 3,
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### High-Contrast Pairs
|
|
513
|
+
|
|
514
|
+
Both `value` and `contrast` support `[normal, highContrast]` pairs:
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
theme.colors({
|
|
518
|
+
surface: { lightness: 95 },
|
|
519
|
+
accent: { lightness: 30 },
|
|
520
|
+
|
|
521
|
+
tint: {
|
|
522
|
+
type: 'mix',
|
|
523
|
+
base: 'surface',
|
|
524
|
+
target: 'accent',
|
|
525
|
+
value: [20, 40], // stronger mix in high-contrast mode
|
|
526
|
+
contrast: [3, 'AAA'], // stricter contrast in high-contrast mode
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Achromatic Colors
|
|
532
|
+
|
|
533
|
+
When mixing with achromatic colors (saturation near zero, e.g., white or black) in `okhsl` space, the hue comes from whichever color has saturation. This prevents meaningless hue artifacts and matches CSS `color-mix()` "missing component" behavior. For purely achromatic mixes, prefer `space: 'srgb'` where hue is irrelevant.
|
|
534
|
+
|
|
535
|
+
### Mix Chaining
|
|
536
|
+
|
|
537
|
+
Mix colors can reference other mix colors, enabling multi-step derivations:
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
theme.colors({
|
|
541
|
+
white: { lightness: 100, saturation: 0 },
|
|
542
|
+
black: { lightness: 0, saturation: 0 },
|
|
543
|
+
gray: { type: 'mix', base: 'white', target: 'black', value: 50, space: 'srgb' },
|
|
544
|
+
lightGray: { type: 'mix', base: 'white', target: 'gray', value: 50, space: 'srgb' },
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Mix colors cannot reference shadow colors (same restriction as regular dependent colors).
|
|
549
|
+
|
|
416
550
|
## Output Formats
|
|
417
551
|
|
|
418
552
|
Control the color format in exports with the `format` option:
|
|
@@ -484,7 +618,7 @@ Modes control how colors adapt across schemes:
|
|
|
484
618
|
|
|
485
619
|
### Lightness
|
|
486
620
|
|
|
487
|
-
|
|
621
|
+
Absolute lightness values (both root colors and dependent colors with absolute lightness) are mapped linearly within the configured `lightLightness` window:
|
|
488
622
|
|
|
489
623
|
```ts
|
|
490
624
|
const [lo, hi] = lightLightness; // default: [10, 100]
|
|
@@ -560,12 +694,12 @@ Combine multiple themes into a single palette:
|
|
|
560
694
|
const palette = glaze.palette({ primary, danger, success, warning });
|
|
561
695
|
```
|
|
562
696
|
|
|
563
|
-
###
|
|
697
|
+
### Prefix Behavior
|
|
564
698
|
|
|
565
|
-
|
|
699
|
+
Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
|
|
566
700
|
|
|
567
701
|
```ts
|
|
568
|
-
const tokens = palette.tokens(
|
|
702
|
+
const tokens = palette.tokens();
|
|
569
703
|
// → {
|
|
570
704
|
// light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
|
|
571
705
|
// dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
|
|
@@ -578,15 +712,44 @@ Custom prefix mapping:
|
|
|
578
712
|
palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
|
|
579
713
|
```
|
|
580
714
|
|
|
715
|
+
To disable prefixing entirely, pass `prefix: false` explicitly. Note that tokens with the same name will overwrite each other (last theme wins).
|
|
716
|
+
|
|
717
|
+
### Primary Theme
|
|
718
|
+
|
|
719
|
+
Use the `primary` option to designate one theme as the primary. Its tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions:
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
const palette = glaze.palette({ primary, danger, success });
|
|
723
|
+
const tokens = palette.tokens({ primary: 'primary' });
|
|
724
|
+
// → {
|
|
725
|
+
// light: {
|
|
726
|
+
// 'primary-surface': 'okhsl(...)', // prefixed (all themes)
|
|
727
|
+
// 'danger-surface': 'okhsl(...)',
|
|
728
|
+
// 'success-surface': 'okhsl(...)',
|
|
729
|
+
// 'surface': 'okhsl(...)', // unprefixed alias (primary only)
|
|
730
|
+
// },
|
|
731
|
+
// }
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
The `primary` option works on `tokens()`, `tasty()`, and `css()`. It combines with any prefix mode — when using a custom prefix map, primary tokens are still duplicated without prefix:
|
|
735
|
+
|
|
736
|
+
```ts
|
|
737
|
+
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }, primary: 'primary' });
|
|
738
|
+
// → 'p-surface' + 'surface' (alias) + 'd-surface'
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
An error is thrown if the primary name doesn't match any theme in the palette.
|
|
742
|
+
|
|
581
743
|
### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
|
|
582
744
|
|
|
583
745
|
The `tasty()` method exports tokens in the [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.):
|
|
584
746
|
|
|
585
747
|
```ts
|
|
586
|
-
const tastyTokens = palette.tasty({
|
|
748
|
+
const tastyTokens = palette.tasty({ primary: 'primary' });
|
|
587
749
|
// → {
|
|
588
750
|
// '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
589
751
|
// '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
752
|
+
// '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' }, // alias
|
|
590
753
|
// }
|
|
591
754
|
```
|
|
592
755
|
|
|
@@ -653,8 +816,10 @@ palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });
|
|
|
653
816
|
|
|
654
817
|
### JSON Export (Framework-Agnostic)
|
|
655
818
|
|
|
819
|
+
JSON export groups by theme name (no prefix needed):
|
|
820
|
+
|
|
656
821
|
```ts
|
|
657
|
-
const data = palette.json(
|
|
822
|
+
const data = palette.json();
|
|
658
823
|
// → {
|
|
659
824
|
// primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
|
|
660
825
|
// danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
|
|
@@ -676,7 +841,7 @@ const css = theme.css();
|
|
|
676
841
|
Use in a stylesheet:
|
|
677
842
|
|
|
678
843
|
```ts
|
|
679
|
-
const css = palette.css({
|
|
844
|
+
const css = palette.css({ primary: 'primary' });
|
|
680
845
|
|
|
681
846
|
const stylesheet = `
|
|
682
847
|
:root { ${css.light} }
|
|
@@ -692,7 +857,8 @@ Options:
|
|
|
692
857
|
|---|---|---|
|
|
693
858
|
| `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
|
|
694
859
|
| `suffix` | `'-color'` | Suffix appended to each CSS property name |
|
|
695
|
-
| `prefix` |
|
|
860
|
+
| `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
|
|
861
|
+
| `primary` | — | (palette only) Theme name to duplicate without prefix |
|
|
696
862
|
|
|
697
863
|
```ts
|
|
698
864
|
// Custom suffix
|
|
@@ -703,9 +869,9 @@ theme.css({ suffix: '' });
|
|
|
703
869
|
theme.css({ format: 'hsl' });
|
|
704
870
|
// → "--surface-color: hsl(...);"
|
|
705
871
|
|
|
706
|
-
// Palette with
|
|
707
|
-
palette.css({
|
|
708
|
-
// → "--primary-surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
872
|
+
// Palette with primary
|
|
873
|
+
palette.css({ primary: 'primary' });
|
|
874
|
+
// → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
709
875
|
```
|
|
710
876
|
|
|
711
877
|
## Output Modes
|
|
@@ -758,10 +924,10 @@ glaze.configure({
|
|
|
758
924
|
|
|
759
925
|
## Color Definition Shape
|
|
760
926
|
|
|
761
|
-
`ColorDef` is a discriminated union of regular colors and
|
|
927
|
+
`ColorDef` is a discriminated union of regular colors, shadow colors, and mix colors:
|
|
762
928
|
|
|
763
929
|
```ts
|
|
764
|
-
type ColorDef = RegularColorDef | ShadowColorDef;
|
|
930
|
+
type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
|
|
765
931
|
|
|
766
932
|
interface RegularColorDef {
|
|
767
933
|
lightness?: HCPair<number | RelativeValue>;
|
|
@@ -780,9 +946,19 @@ interface ShadowColorDef {
|
|
|
780
946
|
intensity: HCPair<number>; // 0–100
|
|
781
947
|
tuning?: ShadowTuning;
|
|
782
948
|
}
|
|
949
|
+
|
|
950
|
+
interface MixColorDef {
|
|
951
|
+
type: 'mix';
|
|
952
|
+
base: string; // "from" color name
|
|
953
|
+
target: string; // "to" color name
|
|
954
|
+
value: HCPair<number>; // 0–100 (mix ratio or opacity)
|
|
955
|
+
blend?: 'opaque' | 'transparent'; // default: 'opaque'
|
|
956
|
+
space?: 'okhsl' | 'srgb'; // default: 'okhsl'
|
|
957
|
+
contrast?: HCPair<MinContrast>;
|
|
958
|
+
}
|
|
783
959
|
```
|
|
784
960
|
|
|
785
|
-
A root color must have absolute `lightness` (a number). A dependent color must have `base`. Relative `lightness` (a string) requires `base`. Shadow colors use `type: 'shadow'` and must reference a non-shadow `bg` color.
|
|
961
|
+
A root color must have absolute `lightness` (a number). A dependent color must have `base`. Relative `lightness` (a string) requires `base`. Shadow colors use `type: 'shadow'` and must reference a non-shadow `bg` color. Mix colors use `type: 'mix'` and must reference two non-shadow colors.
|
|
786
962
|
|
|
787
963
|
## Validation
|
|
788
964
|
|
|
@@ -801,6 +977,12 @@ A root color must have absolute `lightness` (a number). A dependent color must h
|
|
|
801
977
|
| Regular color `base` references a shadow color | Validation error |
|
|
802
978
|
| Shadow `intensity` outside 0–100 | Clamp silently |
|
|
803
979
|
| `contrast` + `opacity` combined | Warning |
|
|
980
|
+
| Mix `base` references non-existent color | Validation error |
|
|
981
|
+
| Mix `target` references non-existent color | Validation error |
|
|
982
|
+
| Mix `base` references a shadow color | Validation error |
|
|
983
|
+
| Mix `target` references a shadow color | Validation error |
|
|
984
|
+
| Mix `value` outside 0–100 | Clamp silently |
|
|
985
|
+
| Circular references involving mix colors | Validation error |
|
|
804
986
|
|
|
805
987
|
## Advanced: Color Math Utilities
|
|
806
988
|
|
|
@@ -846,6 +1028,10 @@ primary.colors({
|
|
|
846
1028
|
'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
|
|
847
1029
|
'shadow-lg': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 20 },
|
|
848
1030
|
|
|
1031
|
+
// Mix colors — hover overlays and tints
|
|
1032
|
+
'hover': { type: 'mix', base: 'surface', target: 'accent-fill', value: 8, blend: 'transparent' },
|
|
1033
|
+
'tint': { type: 'mix', base: 'surface', target: 'accent-fill', value: 20 },
|
|
1034
|
+
|
|
849
1035
|
// Fixed-alpha overlay
|
|
850
1036
|
overlay: { lightness: 0, opacity: 0.5 },
|
|
851
1037
|
});
|
|
@@ -857,16 +1043,16 @@ const note = primary.extend({ hue: 302 });
|
|
|
857
1043
|
|
|
858
1044
|
const palette = glaze.palette({ primary, danger, success, warning, note });
|
|
859
1045
|
|
|
860
|
-
// Export as flat token map grouped by variant
|
|
861
|
-
const tokens = palette.tokens({
|
|
862
|
-
// tokens.light → { 'primary-surface': '
|
|
1046
|
+
// Export as flat token map grouped by variant (prefix defaults to true)
|
|
1047
|
+
const tokens = palette.tokens({ primary: 'primary' });
|
|
1048
|
+
// tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
|
|
863
1049
|
|
|
864
1050
|
// Export as tasty style-to-state bindings (for Tasty style system)
|
|
865
|
-
const tastyTokens = palette.tasty({
|
|
1051
|
+
const tastyTokens = palette.tasty({ primary: 'primary' });
|
|
866
1052
|
|
|
867
1053
|
// Export as CSS custom properties (rgb format by default)
|
|
868
|
-
const css = palette.css({
|
|
869
|
-
// css.light → "--primary-surface-color: rgb(...);\n--
|
|
1054
|
+
const css = palette.css({ primary: 'primary' });
|
|
1055
|
+
// css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
870
1056
|
|
|
871
1057
|
// Standalone shadow computation
|
|
872
1058
|
const v = glaze.shadow({ bg: '#f0eef5', fg: '#1a1a2e', intensity: 10 });
|