npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tenphi/glaze

v0.10.0

Published

OKHSL-based color theme generator with WCAG contrast solving for light, dark, and high-contrast schemes

Readme


Glaze generates robust light, dark, and high-contrast color schemes from a single hue/saturation seed. It preserves WCAG contrast ratios for UI color pairs via explicit dependency declarations — no hidden role math, no magic multipliers.

Features

  • OKHSL color space — perceptually uniform hue and saturation
  • WCAG 2 contrast solving — automatic lightness adjustment to meet AA/AAA targets
  • Mix colors — blend two colors with OKHSL or sRGB interpolation, opaque or transparent, with optional contrast solving
  • Shadow colors — OKHSL-native shadow computation with automatic alpha, fg/bg tinting, and per-scheme adaptation
  • Light + Dark + High-Contrast — all schemes from one definition
  • Per-color hue override — absolute or relative hue shifts within a theme
  • Multi-format outputokhsl, rgb, hsl, oklch with modern CSS space syntax
  • CSS custom properties export — ready-to-use --var: value; declarations per scheme
  • Import/Export — serialize and restore theme configurations
  • Create from hex/RGB — start from an existing brand color
  • Zero dependencies — pure math, runs anywhere (Node.js, browser, edge)
  • Tree-shakeable ESM + CJS — dual-format package
  • TypeScript-first — full type definitions included

Installation

pnpm add @tenphi/glaze
npm install @tenphi/glaze
yarn add @tenphi/glaze

Quick Start

import { glaze } from '@tenphi/glaze';

// Create a theme from a hue (0–360) and saturation (0–100)
const primary = glaze(280, 80);

// Define colors with explicit lightness and contrast relationships
primary.colors({
  surface:       { lightness: 97, saturation: 0.75 },
  text:          { base: 'surface', lightness: '-52', contrast: 'AAA' },
  border:        { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' },
  'accent-fill': { lightness: 52, mode: 'fixed' },
  'accent-text': { base: 'accent-fill', lightness: '+48', contrast: 'AA', mode: 'fixed' },
});

// Create status themes by rotating the hue
const danger  = primary.extend({ hue: 23 });
const success = primary.extend({ hue: 157 });

// Compose into a palette and export
const palette = glaze.palette({ primary, danger, success }, { primary: 'primary' });
const tokens = palette.tokens();
// → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }

Core Concepts

One Theme = One Hue Family

A single glaze theme is tied to one hue/saturation seed. Status colors (danger, success, warning) are derived via extend, which inherits all color definitions and replaces the seed.

Individual colors can override the hue via the hue prop (see Per-Color Hue Override), but the primary purpose of a theme is to scope colors with the same hue.

Color Definitions

Every color is defined explicitly. No implicit roles — every value is stated.

Root Colors (explicit position)

primary.colors({
  surface: { lightness: 97, saturation: 0.75 },
  border:  { lightness: 90, saturation: 0.20 },
});
  • lightness — lightness in the light scheme (0–100)
  • saturation — saturation factor applied to the seed saturation (0–1, default: 1)

Dependent Colors (relative to base)

primary.colors({
  surface: { lightness: 97, saturation: 0.75 },
  text:    { base: 'surface', lightness: '-52', contrast: 'AAA' },
});
  • base — name of another color in the same theme
  • lightness — position of this color (see Lightness Values)
  • contrast — ensures the WCAG contrast ratio meets a target floor against the base

Lightness Values

The lightness prop accepts two forms:

| Form | Example | Meaning | |---|---|---| | Number (absolute) | lightness: 45 | Absolute lightness 0–100 | | String (relative) | lightness: '-52' | Relative to base color's lightness |

Absolute lightness on a dependent color (with base) positions the color independently. In dark mode, it is dark-mapped on its own. The contrast WCAG solver acts as a safety net.

Relative lightness applies a signed delta to the base color's resolved lightness. In dark mode with auto adaptation, the sign flips automatically.

// Relative: 97 - 52 = 45 in light mode
'text': { base: 'surface', lightness: '-52' }

// Absolute: lightness 45 in light mode, dark-mapped independently
'text': { base: 'surface', lightness: 45 }

A dependent color with base but no lightness inherits the base's lightness (equivalent to a delta of 0).

Per-Color Hue Override

Individual colors can override the theme's hue. The hue prop accepts:

| Form | Example | Meaning | |---|---|---| | Number (absolute) | hue: 120 | Absolute hue 0–360 | | String (relative) | hue: '+20' | Relative to the theme seed hue |

Important: Relative hue is always relative to the theme seed hue, not to a base color's hue.

const theme = glaze(280, 80);
theme.colors({
  surface:     { lightness: 97 },
  // Gradient end — slight hue shift from seed (280 + 20 = 300)
  gradientEnd: { lightness: 90, hue: '+20' },
  // Entirely different hue
  warning:     { lightness: 60, hue: 40 },
});

contrast (WCAG Floor)

Ensures the WCAG contrast ratio meets a target floor. Accepts a numeric ratio or a preset string:

type MinContrast = number | 'AA' | 'AAA' | 'AA-large' | 'AAA-large';

| Preset | Ratio | |---|---| | 'AA' | 4.5 | | 'AAA' | 7 | | 'AA-large' | 3 | | 'AAA-large' | 4.5 |

You can also pass any numeric ratio directly (e.g., contrast: 4.5, contrast: 7, contrast: 11).

The constraint is applied independently for each scheme. If the lightness already satisfies the floor, it's kept. Otherwise, the solver adjusts lightness until the target is met.

High-Contrast via Array Values

lightness and contrast accept a [normal, high-contrast] pair:

'border': { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' }
//                                        ↑      ↑
//                                    normal  high-contrast

A single value applies to both modes. All control is local and explicit.

'text':   { base: 'surface', lightness: '-52', contrast: 'AAA' }
'border': { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' }
'muted':  { base: 'surface', lightness: ['-35', '-50'], contrast: ['AA-large', 'AA'] }

Full lightness spectrum in HC mode: In high-contrast variants, the lightLightness and darkLightness window constraints are bypassed entirely. Colors can reach the full 0–100 lightness range, maximizing perceivable contrast. Normal (non-HC) variants continue to use the configured windows.

Theme Color Management

Adding Colors

.colors(defs) performs an additive merge — it adds new colors and overwrites existing ones by name, but does not remove other colors:

const theme = glaze(280, 80);
theme.colors({ surface: { lightness: 97 } });
theme.colors({ text: { lightness: 30 } });
// Both 'surface' and 'text' are now defined

Single Color Getter/Setter

.color(name) returns the definition, .color(name, def) sets it:

theme.color('surface', { lightness: 97, saturation: 0.75 }); // set
const def = theme.color('surface');                     // get → { lightness: 97, saturation: 0.75 }

Removing Colors

.remove(name) or .remove([name1, name2]) deletes color definitions:

theme.remove('surface');
theme.remove(['text', 'border']);

Introspection

theme.has('surface');  // → true/false
theme.list();          // → ['surface', 'text', 'border', ...]

Clearing All Colors

theme.reset(); // removes all color definitions

Import / Export

Serialize a theme's configuration (hue, saturation, color definitions) to a plain JSON-safe object, and restore it later:

// Export
const snapshot = theme.export();
// → { hue: 280, saturation: 80, colors: { surface: { lightness: 97, saturation: 0.75 }, ... } }

const jsonString = JSON.stringify(snapshot);

// Import
const restored = glaze.from(JSON.parse(jsonString));
// restored is a fully functional GlazeTheme

The export contains only the configuration — not resolved color values. Resolved values are recomputed on demand.

Standalone Color Token

Create a single color token without a full theme:

const accent = glaze.color({ hue: 280, saturation: 80, lightness: 52 });

accent.resolve();          // → ResolvedColor with light/dark/lightContrast/darkContrast
accent.token();            // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' }  (tasty format)
accent.tasty();            // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' }  (same as token)
accent.json();             // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
accent.css({ name: 'accent' });
// → { light: '--accent-color: rgb(...);', dark: '--accent-color: rgb(...);', ... }
accent.export();           // → JSON-safe snapshot — pass to `glaze.colorFrom(...)` to rehydrate

Defaults

glaze.color() is tuned for "render this exact color, but adapt the dark variant" — different from theme colors, which are seeds that adapt to both lightness windows. The defaults vary by input form, because string inputs are typically end-user values (color pickers, theme settings) where natural light/dark inversion is the expectation:

  • String value-shorthand (hex, rgb(), hsl(), okhsl(), oklch()):

    • Light variant preserves the input lightness exactly.
    • Dark variant is Möbius-inverted into [globalConfig.darkLightness[0], 100], so glaze.color('#000') renders as #fff in dark mode and glaze.color('#fff') falls to the dark lo floor (default 0.15).
    • Adaptation mode defaults to 'auto'.
    • The dark lo is snapshotted from globalConfig at color-creation time, matching how an explicit scaling.darkLightness: [lo, hi] behaves.
  • Object / tuple value-shorthand ({ h, s, l }, [r, g, b]) and the structured form ({ hue, saturation, lightness, ... }):

    • Light variant preserves the input lightness exactly.
    • Dark variant is linearly mapped into globalConfig.darkLightness (default [15, 95]), snapshotted at color-creation time so later glaze.configure() calls don't retroactively change exported tokens.
    • Adaptation mode defaults to 'fixed' (linear, no Möbius curve).

To opt back into the old fixed-linear default for string inputs, pass either { mode: 'fixed' } as the second arg, or supply an explicit scaling as the third arg (see Lightness scaling).

// Default: pure black inverts to pure white in dark mode.
glaze.color('#000000').tasty();
// → { '': 'okhsl(0 0% 0%)', '@dark': 'okhsl(... 100%)' }

// Opt back into the fixed-linear behavior:
glaze.color('#000000', { mode: 'fixed' }).tasty();
// → { '': 'okhsl(0 0% 0%)', '@dark': 'okhsl(... 15%)' }

Value Shorthand

The first argument can also be a color value — Glaze extracts the seed hue/saturation/lightness for you. All forms support the same exports (resolve / token / tasty / json / css):

// Hex (3, 6, or 8 digits — alpha dropped with warning)
glaze.color('#26fcb2').tasty();
glaze.color('#26fcb2ff').tasty(); // alpha dropped

// CSS color functions Glaze itself emits (`rgb()`, `hsl()`, `okhsl()`, `oklch()`)
// — anything from theme.tasty()/json()/css() round-trips back in.
glaze.color('rgb(38 252 178)').tasty();
glaze.color('hsl(152 97% 57%)').tasty();
glaze.color('okhsl(152 95% 74%)').tasty();
glaze.color('oklch(0.85 0.18 152)').tasty();

// OKHSL object — Glaze's native shape (h: 0–360, s/l: 0–1).
// Passing 0–100 values for s/l throws with a hint to use the
// structured form { hue, saturation, lightness }.
glaze.color({ h: 152, s: 0.95, l: 0.74 }).tasty();

// RGB tuple, 0–255 (same range as glaze.fromRgb).
glaze.color([38, 252, 178]).tasty();

The optional second argument supplies overrides — the WCAG contrast solver, relative hue / lightness, plus the usual seed knobs:

// Brand color seeded from a hex, with saturation/mode overrides
glaze.color('#26fcb2', { saturation: 80, mode: 'fixed' }).tasty();

// Brand text guaranteed AAA against the seed itself.
// Relative `lightness: '+48'` is anchored to the literal seed value.
glaze.color('#1a1a2e', {
  lightness: '+48',
  contrast: 'AAA',
}).tasty();

By default, relative lightness: '+N' and contrast: <ratio> are anchored to the literal seed (the value passed to glaze.color()). Internally Glaze synthesizes a hidden mode: 'static' reference of the seed so the contrast solver compares against the unmapped color across every variant. Pass base (another glaze.color() token) to anchor against another color's resolved variant per scheme instead — see Pairing Colors.

All overrides:

| Option | Notes | |---|---| | hue | Number (absolute 0–360) or '+N'/'-N' (relative to seed — never to base) | | saturation | Override seed saturation (0–100) | | lightness | Number (absolute 0–100) or '+N'/'-N'. Without base, relative is anchored to the literal seed; with base, anchored to base's lightness per scheme. Supports [normal, hc] pairs | | saturationFactor | Multiplier on seed (0–1, default 1) | | mode | 'auto' (default for string inputs) / 'fixed' (default for object / tuple / structured inputs) / 'static' — see Adaptation Modes | | contrast | WCAG floor. Without base, anchored to the literal seed; with base, solved per scheme against base's resolved variant. Same shape as RegularColorDef.contrast. When the target can't be physically met, glaze emits a console.warn and returns the closest passing variant | | base | Another glaze.color() token or a raw GlazeColorValue (hex / rgb() / OkhslColor / [r, g, b]). Raw values are auto-wrapped via glaze.color(value) so they pick up the same auto-invert defaults as an explicit wrap. When set, contrast and relative lightness anchor to it per scheme; relative hue still anchors to the seed | | opacity | Fixed alpha 0–1 applied to every variant. Surfaces in rgb(... / A), okhsl(... / A), etc. Combining with contrast is not recommended (perceived lightness becomes unpredictable) — glaze emits a console.warn | | name | Debug label only — surfaces in error and console.warn messages instead of the internal "value" sentinel. Does not change .token() / .tasty() / .json() / .css() output keys (those still use '', light, etc.). Reserved names ("value", "seed", "externalBase") are rejected |

Alpha components in rgb(... / A) / hsl(... / A) / rgba(...) / hsla(...) and 8-digit hex (#rrggbbaa / #rgba) are parsed but the alpha channel is dropped with a console.warn. To set a fixed alpha on a standalone color, use the opacity override (or opacity on a theme color). Named CSS colors ('red', 'blueviolet') are not supported.

Lightness Scaling

The optional third positional argument lets you override the lightness windows used by glaze.color(). Both keys mirror the field names from GlazeConfig:

// Preserve raw lightness in dark mode too:
glaze.color('#26fcb2', undefined, { darkLightness: false }).tasty();

// Or opt back into a theme-style window:
glaze.color('#26fcb2', undefined, {
  lightLightness: [10, 100],
  darkLightness: [15, 95],
}).tasty();

// Structured form takes scaling as the second positional arg:
glaze
  .color({ hue: 152, saturation: 95, lightness: 74 }, { darkLightness: false })
  .tasty();

| Key | Default for glaze.color() (string input) | Default for glaze.color() (object / tuple / structured) | Effect | |---|---|---|---| | lightLightness | false | false | false = preserve input. Pass [lo, hi] to opt into a remap window. | | darkLightness | [globalConfig.darkLightness[0], 100] (snapshotted; default [15, 100]) | globalConfig.darkLightness (snapshotted; default [15, 95]) | false = preserve input in dark too. Pass [lo, hi] to override the window. |

Note: scaling is all-or-nothing — passing it replaces both fields at once. To keep one field's default, restate it explicitly. The default windows are snapshotted from globalConfig at color-creation time, so later glaze.configure() calls don't retroactively change already-created tokens (and token.export() round-trips byte-for-byte across configure() changes).

Pairing Colors

glaze.color() accepts an optional base override that ties one standalone color to another. When you set base, the WCAG contrast solver and relative lightness offsets switch their anchor from the literal seed to the base's resolved variant per scheme — so the same text color automatically lands at AA against its background in light, dark, and high-contrast modes.

const bg = glaze.color('#1a1a2e');

// Text guaranteed AA against `bg` in every scheme.
const text = glaze.color('#ffffff', { base: bg, contrast: 'AA' });

// Border 8 lightness units lighter than `bg` in each scheme.
const border = glaze.color('#000000', {
  base: bg,
  lightness: '+8',
  mode: 'fixed',
});

base also accepts a raw GlazeColorValue for one-off pairs without a separate token binding:

// Equivalent to `base: glaze.color('#1a1a2e')` — `glaze` auto-wraps it.
const text = glaze.color('#ffffff', { base: '#1a1a2e', contrast: 'AA' });

Behavior with base:

  • contrast is solved per scheme against base's resolved variant (light / dark / lightContrast / darkContrast).
  • Relative lightness: '+N' / '-N' is anchored to base's lightness per scheme (matches theme behavior).
  • Relative hue: '+N' still anchors to the seed (the value passed to glaze.color()), not the base. Absolute hue overrides take precedence as usual.
  • mode works as a per-pair knob — pass mode: 'fixed' to disable Möbius inversion for the dependent color, or mode: 'auto' to keep it (defaults follow the same string-vs-object rules as standalone).
  • The base token's .resolve() is called lazily on the first resolve of the dependent and the result is captured by reference; later mutations to the base don't apply (matches existing snapshot semantics for scaling.darkLightness).
  • Raw value bases (base: '#fff', base: { h, s, l }, base: [r, g, b]) are auto-wrapped via glaze.color(value) and inherit the same string-vs-object defaults. To skip auto-invert on the base, wrap it yourself: base: glaze.color(value, undefined, { darkLightness: false }).
  • When the contrast target is physically unreachable (e.g. AAA against a mid-grey base), glaze emits a single console.warn per (name, scheme, target) triple and returns the closest passing variant. Use the name override to make the warning more identifiable in your logs.

Chains compose:

const bg = glaze.color('#000000');
const surface = glaze.color('#222222', { base: bg, contrast: 'AAA' });
const text = glaze.color('#ffffff', { base: surface, contrast: 'AA' });
// Each level meets its contrast budget against its base in every scheme.

Naming Standalone Colors

The name override is a debug label, not an output key:

const cardBg = glaze.color('#1a1a2e', {
  name: 'card-bg',           // surfaces in `console.warn` / Error messages
});

cardBg.token();              // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' }
cardBg.json();               // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
cardBg.css({ name: 'card' }); // CSS variable name comes from `css({ name })`,
                              // NOT from the override above

Use it to make warnings traceable when you have many glaze.color() calls in a project — without it, glaze falls back to the internal sentinel "value":

// With name:
// > glaze: color "card-bg" cannot meet contrast "AAA" (7.00) in dark scheme...

// Without name:
// > glaze: color "value" cannot meet contrast "AAA" (7.00) in dark scheme...

The reserved internal sentinels ("value", "seed", "externalBase") are rejected with a clear error pointing at the conflict.

Persisting Standalone Colors

glaze.color() tokens can be serialized to JSON-safe data and rehydrated later — useful for color pickers, theme settings UIs, and URL state.

const text = glaze.color('#1a1a1a', {
  contrast: 'AA',
  opacity: 0.9,
  name: 'profile-text',
});

const data = text.export();         // JSON-safe snapshot
const json = JSON.stringify(data);   // ship to localStorage / API / URL
const restored = glaze.colorFrom(JSON.parse(json));
// `restored.resolve()` matches `text.resolve()` byte-for-byte.

The export captures the original value, all overrides, and the effective scaling (snapshotted from globalConfig at create time so later glaze.configure() calls don't change exported tokens). Token-typed base is recursively serialized, value-typed base is preserved as the raw value.

Both forms round-trip:

// Value form
const a = glaze.color('#26fcb2', { contrast: 'AA' });
const aBack = glaze.colorFrom(a.export());

// Structured form
const b = glaze.color({
  hue: 280,
  saturation: 50,
  lightness: 50,
  opacity: 0.5,
});
const bBack = glaze.colorFrom(b.export());

From Existing Colors

Create a theme from an existing brand color by extracting its OKHSL hue and saturation:

// From hex
const brand = glaze.fromHex('#7a4dbf');

// From RGB (0–255)
const brand = glaze.fromRgb(122, 77, 191);

The resulting theme has the extracted hue and saturation. Add colors as usual:

brand.colors({
  surface: { lightness: 97, saturation: 0.75 },
  text:    { base: 'surface', lightness: '-52', contrast: 'AAA' },
});

Shadow Colors

Shadow colors are colors with computed alpha. Instead of a parallel shadow system, they extend the existing color pipeline. All math is done natively in OKHSL.

Defining Shadow Colors

Shadow colors use type: 'shadow' and reference a bg (background) color and optionally an fg (foreground) color for tinting and intensity modulation:

theme.colors({
  surface: { lightness: 95 },
  text:    { base: 'surface', lightness: '-52', contrast: 'AAA' },

  'shadow-sm': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 5 },
  'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
  'shadow-lg': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 20 },
});

Shadow colors are included in all output methods (tokens(), tasty(), css(), json()) alongside regular colors:

theme.tokens({ format: 'oklch' });
// light: { 'shadow-md': 'oklch(0.15 0.009 282 / 0.1)', ... }
// dark:  { 'shadow-md': 'oklch(0.06 0.004 0 / 0.49)', ... }

How Shadows Work

The shadow algorithm computes a dark, low-saturation pigment color and an alpha value that produces the desired visual intensity:

  1. Contrast weight — when fg is provided, shadow strength scales with |l_bg - l_fg|. Dark text on a light background produces a strong shadow; near-background-lightness elements produce barely visible shadows.
  2. Pigment color — hue blended between fg and bg, low saturation, dark lightness.
  3. Alpha — computed via a tanh curve that saturates smoothly toward alphaMax (default 0.6), ensuring well-separated shadow levels even on dark backgrounds.

Achromatic Shadows

Omit fg for a pure achromatic shadow at full user-specified intensity:

theme.colors({
  'drop-shadow': { type: 'shadow', bg: 'surface', intensity: 12 },
});

High-Contrast Intensity

intensity supports [normal, highContrast] pairs:

theme.colors({
  'shadow-card': { type: 'shadow', bg: 'surface', fg: 'text', intensity: [10, 20] },
});

Fixed Opacity (Regular Colors)

For a simple fixed-alpha color (no shadow algorithm), use opacity on a regular color:

theme.colors({
  overlay: { lightness: 0, opacity: 0.5 },
});
// → 'oklch(0 0 0 / 0.5)'

Shadow Tuning

Fine-tune shadow behavior per-color or globally:

// Per-color tuning
theme.colors({
  'shadow-soft': {
    type: 'shadow', bg: 'surface', intensity: 10,
    tuning: { alphaMax: 0.3, saturationFactor: 0.1 },
  },
});

// Global tuning
glaze.configure({
  shadowTuning: { alphaMax: 0.5, bgHueBlend: 0.3 },
});

Available tuning parameters:

| Parameter | Default | Description | |---|---|---| | saturationFactor | 0.18 | Fraction of fg saturation kept in pigment | | maxSaturation | 0.25 | Upper clamp on pigment saturation | | lightnessFactor | 0.25 | Multiplier for bg lightness to pigment lightness | | lightnessBounds | [0.05, 0.20] | Clamp range for pigment lightness | | minGapTarget | 0.05 | Target minimum gap between pigment and bg lightness | | alphaMax | 0.6 | Asymptotic maximum alpha | | bgHueBlend | 0.2 | Blend weight pulling pigment hue toward bg hue |

Standalone Shadow Computation

Compute a shadow outside of a theme. bg and fg accept any GlazeColorValue: hex (#rgb / #rrggbb / #rrggbbaa), rgb() / hsl() / okhsl() / oklch() strings, OKHSL objects, or [r, g, b] (0–255) tuples.

const v = glaze.shadow({
  bg: '#f0eef5',
  fg: '#1a1a2e',
  intensity: 10,
});
// → { h: 280, s: 0.14, l: 0.2, alpha: 0.1 }

// Equivalent with non-hex inputs:
glaze.shadow({
  bg: 'rgb(240 238 245)',
  fg: { h: 280, s: 0.06, l: 0.13 },
  intensity: 10,
});

const css = glaze.format(v, 'oklch');
// → 'oklch(0.15 0.014 280 / 0.1)'

Consuming in CSS

.card {
  box-shadow: 0 2px 6px var(--shadow-sm-color),
              0 8px 24px var(--shadow-md-color);
}

Mix Colors

Mix colors blend two existing colors together. Use them for hover overlays, tints, shades, and any derived color that sits between two reference colors.

Opaque Mix

Produces a solid color by interpolating between base and target:

theme.colors({
  surface: { lightness: 95 },
  accent:  { lightness: 30 },

  // 30% of the way from surface toward accent
  tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
});
  • value — mix ratio 0–100 (0 = pure base, 100 = pure target)
  • The result is a fully opaque color (alpha = 1)
  • Adapts to light/dark/HC schemes automatically via the resolved base and target

Transparent Mix

Produces the target color with a controlled opacity — useful for hover overlays:

theme.colors({
  surface: { lightness: 95 },
  black:   { lightness: 0, saturation: 0 },

  hover: {
    type: 'mix',
    base: 'surface',
    target: 'black',
    value: 8,
    blend: 'transparent',
  },
});
// hover → target color (black) with alpha = 0.08

The output color has h, s, l from the target and alpha = value / 100.

Blend Space

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:

theme.colors({
  surface: { lightness: 95 },
  accent:  { lightness: 30 },

  // sRGB blend — matches what the browser would render
  hover: { type: 'mix', base: 'surface', target: 'accent', value: 20, space: 'srgb' },
});

| Space | Behavior | Best for | |---|---|---| | 'okhsl' (default) | Perceptually uniform OKHSL interpolation | Design token derivation | | 'srgb' | Linear sRGB channel interpolation | Matching browser compositing |

The space option only affects opaque blending. Transparent blending always composites in linear sRGB (matching browser alpha compositing).

Contrast Solving

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:

theme.colors({
  surface: { lightness: 95 },
  accent:  { lightness: 30 },

  // Ensure the mixed color has at least AA contrast against surface
  tint: {
    type: 'mix',
    base: 'surface',
    target: 'accent',
    value: 10,
    contrast: 'AA',
  },

  // Ensure the transparent overlay has at least 3:1 contrast
  overlay: {
    type: 'mix',
    base: 'surface',
    target: 'accent',
    value: 5,
    blend: 'transparent',
    contrast: 3,
  },
});

High-Contrast Pairs

Both value and contrast support [normal, highContrast] pairs:

theme.colors({
  surface: { lightness: 95 },
  accent:  { lightness: 30 },

  tint: {
    type: 'mix',
    base: 'surface',
    target: 'accent',
    value: [20, 40],          // stronger mix in high-contrast mode
    contrast: [3, 'AAA'],     // stricter contrast in high-contrast mode
  },
});

Achromatic Colors

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.

Mix Chaining

Mix colors can reference other mix colors, enabling multi-step derivations:

theme.colors({
  white: { lightness: 100, saturation: 0 },
  black: { lightness: 0, saturation: 0 },
  gray:  { type: 'mix', base: 'white', target: 'black', value: 50, space: 'srgb' },
  lightGray: { type: 'mix', base: 'white', target: 'gray', value: 50, space: 'srgb' },
});

Mix colors cannot reference shadow colors (same restriction as regular dependent colors).

Output Formats

Control the color format in exports with the format option:

// Default: OKHSL
theme.tokens();                        // → 'okhsl(280 60% 97%)'

// RGB (modern space syntax, rounded integers)
theme.tokens({ format: 'rgb' });       // → 'rgb(244 240 250)'

// HSL (modern space syntax)
theme.tokens({ format: 'hsl' });       // → 'hsl(270.5 45.2% 95.8%)'

// OKLCH
theme.tokens({ format: 'oklch' });     // → 'oklch(0.965 0.0123 280)'

The format option works on all export methods: theme.tokens(), theme.tasty(), theme.json(), theme.css(), palette.tokens(), palette.tasty(), palette.json(), palette.css(), and standalone glaze.color().token() / .tasty() / .json() / .css().

Colors with alpha < 1 (shadow colors, or regular colors with opacity) include an alpha component:

// → 'oklch(0.15 0.009 282 / 0.1)'
// → 'rgb(34 28 42 / 0.1)'

Available formats:

| Format | Output (alpha = 1) | Output (alpha < 1) | Notes | |---|---|---|---| | 'okhsl' (default) | okhsl(H S% L%) | okhsl(H S% L% / A) | Native format, not a CSS function | | 'rgb' | rgb(R G B) | rgb(R G B / A) | Rounded integers, space syntax | | 'hsl' | hsl(H S% L%) | hsl(H S% L% / A) | Modern space syntax | | 'oklch' | oklch(L C H) | oklch(L C H / A) | OKLab-based LCH |

All numeric output strips trailing zeros for cleaner CSS (e.g., 95 not 95.0).

Adaptation Modes

Modes control how colors adapt across schemes:

| Mode | Behavior | |---|---| | 'auto' (default) | Full adaptation. Light ↔ dark inversion. High-contrast boost. | | 'fixed' | Color stays recognizable. Only safety corrections. For brand buttons, CTAs. | | 'static' | No adaptation. Same value in every scheme. |

How Relative Lightness Adapts

auto mode — relative lightness sign flips in dark scheme:

// Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
// Dark:  surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
//        contrast solver may push further (light text on dark bg)

fixed mode — lightness is mapped (not inverted), relative sign preserved:

// Light: accent-fill L=52, accent-text lightness='+48' → L=100 (white on brand)
// Dark:  accent-fill maps to L≈51.6, sign preserved → L≈99.6

static mode — no adaptation, same value in every scheme.

Light Scheme Mapping

Lightness

Absolute lightness values (both root colors and dependent colors with absolute lightness) are mapped linearly within the configured lightLightness window:

const [lo, hi] = lightLightness; // default: [10, 100]
const mappedL = (lightness * (hi - lo)) / 100 + lo;

Both auto and fixed modes use the same linear formula. static mode and high-contrast variants bypass the mapping entirely (identity: mappedL = l).

| Color | Raw L | Mapped L (default [10, 100]) | |---|---|---| | surface (L=97) | 97 | 97.3 | | accent-fill (L=52) | 52 | 56.8 | | near-black (L=0) | 0 | 10 |

Dark Scheme Mapping

Lightness

auto — inverted with a Möbius transformation within the configured window:

const [lo, hi] = darkLightness; // default: [15, 95]
const t = (100 - lightness) / 100;
const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5

The darkCurve parameter (default 0.5, range 0–1) controls how much the dark-mode inversion expands lightness deltas. Lower values produce stronger expansion; 1 gives linear (legacy) behavior. Accepts a [normal, highContrast] pair for separate HC tuning (e.g. darkCurve: [0.5, 0.3]); a single number applies to both. Unlike a power curve, the Möbius transformation provides proportional expansion — small and large deltas are scaled by similar ratios, preserving the visual hierarchy of the light theme.

fixed — mapped without inversion (not affected by darkCurve):

const mappedL = (lightness * (hi - lo)) / 100 + lo;

| Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) | |---|---|---|---|---| | surface (L=97) | 97 | 19.7 | 17.4 | 92.6 | | accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 | | accent-text (L=100) | 100 | 15 | 15 | 95 |

In high-contrast variants, the darkLightness window is bypassed — auto uses the Möbius curve over the full [0, 100] range, and fixed uses identity (L). To use a different curve shape for HC, pass a [normal, hc] pair to darkCurve (e.g. darkCurve: [0.5, 0.3]).

Saturation

darkDesaturation reduces saturation for all colors in dark scheme:

S_dark = S_light * (1 - darkDesaturation) // default: 0.1

Inherited Themes (extend)

extend creates a new theme inheriting all color definitions, replacing the hue and/or saturation seed:

const primary = glaze(280, 80);
primary.colors({ /* ... */ });

const danger  = primary.extend({ hue: 23 });
const success = primary.extend({ hue: 157 });
const warning = primary.extend({ hue: 84 });

Override individual colors (additive merge):

const danger = primary.extend({
  hue: 23,
  colors: { 'accent-fill': { lightness: 48, mode: 'fixed' } },
});

Palette Composition

Combine multiple themes into a single palette:

const palette = glaze.palette({ primary, danger, success, warning });

Optionally designate a primary theme at creation time:

const palette = glaze.palette(
  { primary, danger, success, warning },
  { primary: 'primary' },
);

Prefix Behavior

Palette export methods (tokens(), tasty(), css()) default to prefix: true — all tokens are automatically prefixed with the theme name to avoid collisions:

const tokens = palette.tokens();
// → {
//   light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
//   dark:  { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
// }

Custom prefix mapping:

palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });

To disable prefixing entirely, pass prefix: false explicitly.

Collision Detection

When two themes produce the same output key (via prefix: false, custom prefix maps, or primary unprefixed aliases), the first-written value wins and a console.warn is emitted:

const palette = glaze.palette({ a, b });
palette.tokens({ prefix: false });
// ⚠ glaze: token "surface" from theme "b" collides with theme "a" — skipping.

Primary Theme

The primary theme's tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions. Set at palette creation to apply to all exports automatically:

const palette = glaze.palette(
  { primary, danger, success },
  { primary: 'primary' },
);
const tokens = palette.tokens();
// → {
//   light: {
//     'primary-surface': 'okhsl(...)',  // prefixed (all themes)
//     'danger-surface':  'okhsl(...)',
//     'success-surface': 'okhsl(...)',
//     'surface':         'okhsl(...)',  // unprefixed alias (primary only)
//   },
// }

Override or disable per-export:

palette.tokens({ primary: 'danger' });  // use danger as primary for this call
palette.tokens({ primary: false });     // no primary for this call

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:

palette.tokens({ prefix: { primary: 'p-', danger: 'd-' } });
// → 'p-surface' + 'surface' (alias from palette-level primary) + 'd-surface'

An error is thrown if the primary name doesn't match any theme in the palette.

Tasty Export (for Tasty style system)

The tasty() method exports tokens in the Tasty style-to-state binding format — #name color token keys with state aliases ('', @dark, etc.). See the Playground for live examples of Glaze integration:

const palette = glaze.palette(
  { primary, danger, success },
  { primary: 'primary' },
);
const tastyTokens = palette.tasty();
// → {
//   '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
//   '#danger-surface':  { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
//   '#surface':         { '': 'okhsl(...)', '@dark': 'okhsl(...)' },  // alias
// }

Apply as global styles to make color tokens available app-wide:

import { useGlobalStyles } from '@cube-dev/ui-kit';

// In your root component
useGlobalStyles('body', tastyTokens);

For zero-runtime builds, use tastyStatic to generate the CSS at build time:

import { tastyStatic } from '@cube-dev/ui-kit';

tastyStatic('body', tastyTokens);

Alternatively, register as a recipe via configure():

import { configure, tasty } from '@cube-dev/ui-kit';

configure({
  recipes: {
    'all-themes': tastyTokens,
  },
});

const Page = tasty({
  styles: {
    recipe: 'all-themes',
    fill: '#primary-surface',
    color: '#primary-text',
  },
});

Or spread directly into component styles:

const Card = tasty({
  styles: {
    ...tastyTokens,
    fill: '#primary-surface',
    color: '#primary-text',
  },
});

Custom prefix mapping:

palette.tasty({ prefix: { primary: 'brand-', danger: 'error-' } });

Custom state aliases:

palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });

JSON Export (Framework-Agnostic)

JSON export groups by theme name (no prefix needed):

const data = palette.json();
// → {
//   primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
//   danger:  { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
// }

CSS Export

Export as CSS custom property declarations, grouped by scheme variant. Each variant is a string of --name-color: value; lines that you can wrap in your own selectors and media queries.

const css = theme.css();
// css.light        → "--surface-color: rgb(...);\n--text-color: rgb(...);"
// css.dark         → "--surface-color: rgb(...);\n--text-color: rgb(...);"
// css.lightContrast → "--surface-color: rgb(...);\n--text-color: rgb(...);"
// css.darkContrast  → "--surface-color: rgb(...);\n--text-color: rgb(...);"

Use in a stylesheet:

const palette = glaze.palette(
  { primary, danger, success },
  { primary: 'primary' },
);
const css = palette.css();

const stylesheet = `
:root { ${css.light} }
@media (prefers-color-scheme: dark) {
  :root { ${css.dark} }
}
`;

Options:

| Option | Default | Description | |---|---|---| | format | 'rgb' | Color format ('rgb', 'hsl', 'okhsl', 'oklch') | | suffix | '-color' | Suffix appended to each CSS property name | | prefix | true (palette) | (palette only) true uses "<themeName>-", or provide a custom map | | primary | inherited | (palette only) Override or disable (false) the palette-level primary for this call |

// Custom suffix
theme.css({ suffix: '' });
// → "--surface: rgb(...);"

// Custom format
theme.css({ format: 'hsl' });
// → "--surface-color: hsl(...);"

// Palette with primary (inherited from palette creation)
palette.css();
// → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"

Output Modes

Control which scheme variants appear in exports:

// Light only
palette.tokens({ modes: { dark: false, highContrast: false } });
// → { light: { ... } }

// Light + dark (default)
palette.tokens({ modes: { highContrast: false } });
// → { light: { ... }, dark: { ... } }

// All four variants
palette.tokens({ modes: { dark: true, highContrast: true } });
// → { light: { ... }, dark: { ... }, lightContrast: { ... }, darkContrast: { ... } }

The modes option works the same way on tokens(), tasty(), json(), and css().

Resolution priority (highest first):

  1. tokens({ modes }) / tasty({ modes }) / json({ modes }) / css({ ... }) — per-call override
  2. glaze.configure({ modes }) — global config
  3. Built-in default: { dark: true, highContrast: false }

Configuration

glaze.configure({
  lightLightness: [10, 100],   // Light scheme lightness window [lo, hi] (bypassed in HC)
  darkLightness: [15, 95],     // Dark scheme lightness window [lo, hi] (bypassed in HC)
  darkDesaturation: 0.1,       // Saturation reduction in dark scheme (0–1)
  darkCurve: 0.5,              // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
  states: {
    dark: '@dark',             // State alias for dark mode tokens
    highContrast: '@high-contrast',
  },
  modes: {
    dark: true,                // Include dark variants in exports
    highContrast: false,       // Include high-contrast variants
  },
  shadowTuning: {              // Default tuning for all shadow colors
    alphaMax: 0.6,
    bgHueBlend: 0.2,
  },
});

Color Definition Shape

ColorDef is a discriminated union of regular colors, shadow colors, and mix colors:

type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;

interface RegularColorDef {
  lightness?: HCPair<number | RelativeValue>;
  saturation?: number;
  hue?: number | RelativeValue;
  base?: string;
  contrast?: HCPair<MinContrast>;
  mode?: 'auto' | 'fixed' | 'static';
  opacity?: number; // fixed alpha (0–1)
}

interface ShadowColorDef {
  type: 'shadow';
  bg: string;       // background color name (non-shadow)
  fg?: string;      // foreground color name (non-shadow)
  intensity: HCPair<number>; // 0–100
  tuning?: ShadowTuning;
}

interface MixColorDef {
  type: 'mix';
  base: string;     // "from" color name
  target: string;   // "to" color name
  value: HCPair<number>;     // 0–100 (mix ratio or opacity)
  blend?: 'opaque' | 'transparent'; // default: 'opaque'
  space?: 'okhsl' | 'srgb';        // default: 'okhsl'
  contrast?: HCPair<MinContrast>;
}

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.

Validation

| Condition | Behavior | |---|---| | contrast without base | Validation error | | Relative lightness without base | Validation error | | lightness resolves outside 0–100 | Clamp silently | | saturation outside 0–1 | Clamp silently | | Circular base references | Validation error | | base references non-existent name | Validation error | | Shadow bg references non-existent color | Validation error | | Shadow fg references non-existent color | Validation error | | Shadow bg references another shadow color | Validation error | | Shadow fg references another shadow color | Validation error | | Regular color base references a shadow color | Validation error | | Shadow intensity outside 0–100 | Clamp silently | | contrast + opacity combined | Warning | | Mix base references non-existent color | Validation error | | Mix target references non-existent color | Validation error | | Mix base references a shadow color | Validation error | | Mix target references a shadow color | Validation error | | Mix value outside 0–100 | Clamp silently | | Circular references involving mix colors | Validation error |

Advanced: Color Math Utilities

Glaze re-exports its internal color math for advanced use:

import {
  okhslToLinearSrgb,
  okhslToSrgb,
  okhslToOklab,
  srgbToOkhsl,
  parseHex,
  relativeLuminanceFromLinearRgb,
  contrastRatioFromLuminance,
  formatOkhsl,
  formatRgb,
  formatHsl,
  formatOklch,
  findLightnessForContrast,
  resolveMinContrast,
} from '@tenphi/glaze';

Full Example

import { glaze } from '@tenphi/glaze';

const primary = glaze(280, 80);

primary.colors({
  surface:       { lightness: 97, saturation: 0.75 },
  text:          { base: 'surface', lightness: '-52', contrast: 'AAA' },
  border:        { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' },
  bg:            { lightness: 97, saturation: 0.75 },
  icon:          { lightness: 60, saturation: 0.94 },
  'accent-fill': { lightness: 52, mode: 'fixed' },
  'accent-text': { base: 'accent-fill', lightness: '+48', contrast: 'AA', mode: 'fixed' },
  disabled:      { lightness: 81, saturation: 0.4 },

  // Shadow colors — computed alpha, automatic dark-mode adaptation
  'shadow-sm':   { type: 'shadow', bg: 'surface', fg: 'text', intensity: 5 },
  'shadow-md':   { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
  'shadow-lg':   { type: 'shadow', bg: 'surface', fg: 'text', intensity: 20 },

  // Mix colors — hover overlays and tints
  'hover':       { type: 'mix', base: 'surface', target: 'accent-fill', value: 8, blend: 'transparent' },
  'tint':        { type: 'mix', base: 'surface', target: 'accent-fill', value: 20 },

  // Fixed-alpha overlay
  overlay:       { lightness: 0, opacity: 0.5 },
});

const danger  = primary.extend({ hue: 23 });
const success = primary.extend({ hue: 157 });
const warning = primary.extend({ hue: 84 });
const note    = primary.extend({ hue: 302 });

const palette = glaze.palette(
  { primary, danger, success, warning, note },
  { primary: 'primary' },
);

// Export as flat token map grouped by variant (prefix defaults to true)
const tokens = palette.tokens();
// tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }

// Export as tasty style-to-state bindings (for Tasty style system)
const tastyTokens = palette.tasty();

// Export as CSS custom properties (rgb format by default)
const css = palette.css();
// css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"

// Standalone shadow computation
const v = glaze.shadow({ bg: '#f0eef5', fg: '#1a1a2e', intensity: 10 });
const shadowCss = glaze.format(v, 'oklch');
// → 'oklch(0.15 0.014 280 / 0.1)'

// Save and restore a theme
const snapshot = primary.export();
const restored = glaze.from(snapshot);

// Create from an existing brand color
const brand = glaze.fromHex('#7a4dbf');
brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '-52' } });

API Reference

Theme Creation

| Method | Description | |---|---| | glaze(hue, saturation?) | Create a theme from hue (0–360) and saturation (0–100) | | glaze({ hue, saturation }) | Create a theme from an options object | | glaze.from(data) | Create a theme from an exported configuration | | glaze.fromHex(hex) | Create a theme from a hex color (#rgb or #rrggbb) | | glaze.fromRgb(r, g, b) | Create a theme from RGB values (0–255) | | glaze.color(input, scaling?) | Create a standalone color token from { hue, saturation, lightness, opacity?, contrast?, base?, name?, ... }. Optional scaling overrides the lightness windows | | glaze.color(value, overrides?, scaling?) | Create a standalone color token from a hex string (3/6/8 digits), an rgb() / hsl() / okhsl() / oklch() string, an { h, s, l } OKHSL object, or an [r, g, b] (0–255) tuple. Overrides accept absolute or relative hue / lightness, saturation, mode, contrast, opacity, name, and base (a GlazeColorToken or any GlazeColorValue; raw values are auto-wrapped). When base is set, contrast and relative lightness are anchored to the base per scheme — see Pairing Colors. String inputs default to mode: 'auto' with the dark window extended to upper 100; object / tuple inputs default to mode: 'fixed'. | | glaze.colorFrom(data) | Rehydrate a glaze.color() token from a .export() snapshot. Inverse of token.export() — see Persisting Standalone Colors | | glaze.shadow(input) | Compute a standalone shadow color (returns ResolvedColorVariant). bg / fg accept any GlazeColorValue form | | glaze.format(variant, format?) | Format any ResolvedColorVariant as a CSS string |

Theme Methods

| Method | Description | |---|---| | theme.colors(defs) | Add/replace colors (additive merge) | | theme.color(name) | Get a color definition | | theme.color(name, def) | Set a single color definition | | theme.remove(names) | Remove one or more colors | | theme.has(name) | Check if a color is defined | | theme.list() | List all defined color names | | theme.reset() | Clear all color definitions | | theme.export() | Export configuration as JSON-safe object | | theme.extend(options) | Create a child theme | | theme.resolve() | Resolve all colors | | theme.tokens(options?) | Export as flat token map grouped by variant | | theme.tasty(options?) | Export as Tasty style-to-state bindings | | theme.json(options?) | Export as plain JSON | | theme.css(options?) | Export as CSS custom property declarations |

Global Configuration

| Method | Description | |---|---| | glaze.configure(config) | Set global configuration | | glaze.palette(themes, options?) | Compose themes into a palette (options: { primary? }) | | glaze.getConfig() | Get current global config | | glaze.resetConfig() | Reset to defaults |

License

MIT