Skip to content

Tokens & Overrides

Theme tokens come in two layers. Layer 1 is the palette (--hz-palette-*): single-value hues, authored per theme. Layer 2 is the semantic roles (--hz-color-*) and intents (--hz-intent-*), pure var() indirection that chains through Layer 1. Override one hue and the change follows everywhere it is used.

Remap or extend Layer 2 directly when you want different wiring instead of a palette override. Token names and defaults live on Colors & Intent. Duration and easing tokens (--hz-duration-* / --hz-ease-*) follow the same override rules and are documented on Motion. That page also introduces @hyzer-labs/ui/motion: script-side helpers built on those tokens, including transitions, a scroll-reveal attachment, and a view-transition wrapper.

In plain CSS, with no build step

Import your stylesheet after tokens.css and redefine what you need. These are the four recipes that cover nearly everything:

/* Override any hue once, in the --hz-palette-* namespace; every role
   and intent that references it follows automatically. */
:root {
	--hz-palette-primary: #0f766e;
	--hz-palette-gray: #64748b; /* border, text-muted, and surface tints follow */
}

--hz-density above rescales the whole density rhythm from one unit. To point each nesting depth at your own spacing values instead, see Spacing & Sizing → Bring your own scale.

Or describe it once, in a config

Everything above is hand-written CSS, which is the whole point of this tier: no build step, no tooling. You can instead keep your system in one typed file and have the sheet generated for you, with every pairing contrast-graded on each run. Config & CLI covers hyzer.config.ts end to end: the option surface, the hyzer generate modes, the trimmed icon barrel, and the utilities sheet.

The two routes reach the same place. A config resolves to the same two-layer model this page describes, so nothing you learn here is wasted if you adopt one later.

A hue you set under tokens changes the default theme only. The library's own dark theme is a complete, contrast-tuned set, and it keeps its value for anything it already covers. To carry a change into dark, set it again under themes.dark, the way the sample on Config & CLI does for primary.

The plain-CSS route above works differently. A :root rule of your own comes after tokens.css, so it lands in dark at the page level too. That is why the Dark mode recipe writes a [data-theme='dark'] rule instead. Generate a sheet and it writes that rule for you.

Verify your palette

The contrast math behind the generate report is public API. contrastRatio gives the raw WCAG ratio for two colors. gradeContrast turns a foreground/background pair into pass/fail grades per level and text size. bestLevel and bestLevelLarge name the best level a ratio reaches. parseColor, mixSrgb, relativeLuminance, hexToRgb, and rgbToHex are the conversion pieces.

Write your colors in whatever space suits you

These functions read oklch(), hsl(), lab(), color(display-p3 …), a named color and a hex alike. The config reads all of those, plus color-mix() in all fifteen spaces CSS can interpolate in. It also reads relative colors: oklch(from var(--brand) calc(l * 0.8) c h). WCAG defines contrast on sRGB, so a color outside that gamut is graded on its clipped equivalent, and the generate report names those pairings.

A color that cannot stand on its own is refused rather than guessed at: a var(), a currentColor, or anything part-transparent. A translucent color has no ratio until it sits on a known backdrop. Refused means a thrown TypeError naming the value, so a test that passes one fails loudly instead of comparing a number that was never a ratio. (parseColor is the exception: it reports the same refusal by returning null.)

All of them are pure functions over strings, with no DOM access, so they are safe to run on the server. They are exported from the package root and @hyzer-labs/ui/utils, and the resolved palette is importable from @hyzer-labs/ui/tokens.

Assert the pairings your override touches in a unit test:

import { gradeContrast, contrastRatio, mixSrgb } from '@hyzer-labs/ui';
import { palette } from '@hyzer-labs/ui/tokens';

// Your override for --hz-palette-primary
const brand = '#0f766e';

gradeContrast(brand, palette.white).aaNormal; // text on surface
gradeContrast(palette.white, brand).aaNormal; // solid button text

// On surface-muted: the same 6% color-mix the theme derives
contrastRatio(brand, mixSrgb(palette.gray, palette.white, 0.06));

For the full methodology and a live pairing checker over every shipped token, see Contrast & Accessibility.