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.

Dark may override any tier in [data-theme='dark'], including the palette. Your components and the reference theme never read --hz-palette-* directly, so they keep resolving through roles and intents either way.
Dark is not a special case in the config, either: it is one entry in a themes map, and you can add as many more as you like. Section themes covers naming them and scoping one to part of a page.

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 */
}

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.

Verify your palette

The contrast math behind the generate report is public API. contrastRatio gives the raw WCAG ratio for two hex 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. mixSrgb, relativeLuminance, hexToRgb, and rgbToHex are the conversion pieces.

All of them are pure functions over hex 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.