Skip to content

Config & CLI

An optional file. hyzer.config.ts describes your design system once. The hyzer CLI turns it into a token sheet, a trimmed icon barrel, and an optional utility sheet. Every run checks the result against WCAG AA. Skip the file and you get the defaults.

The config file and the CLI are both optional

You do not need either one to theme this library. Plain CSS overrides work on their own. Tokens & Overrides covers that route. Reach for a config when you want your system in one typed file, contrast graded on every build, and a sheet you regenerate rather than maintain.

Describe your system in one file

The same engine that generates this library's own tokens.css ships in the package. Describe your system once in hyzer.config.ts. Overrides merge over the base schema, and new keys extend it. Nested tokens.palette objects generate ramps (--hz-palette-brand-red-900) even though the base palette ships none.

ts hyzer.config.ts
import { defineConfig } from '@hyzer-labs/ui/config';

export default defineConfig({
	output: 'src/styles/tokens.css',

	// Everything under `tokens` is the DEFAULT theme: it lands in the
	// :root block, which is what a page gets with no data-theme set.
	tokens: {
		palette: {
			primary: '#0f766e',                                          // override
			fairway: '#3f6212',                                          // add a hue
			brandRed: { 50: '#fef2f2', 500: '#b91c1c', 900: '#7f1d1d' }  // add a ramp
		},
		intent: {
			fairway: 'var(--hz-palette-fairway)',      // add an intent
			// Intents are graded, so aim at a ramp's middle. Point one at
			// the pale or dark end and the report fails that pairing.
			brand: 'var(--hz-palette-brand-red-500)'
		},
		typography: { fontFamily: { sans: "'Inter', system-ui, sans-serif" } },
		density: { unit: '0.5rem' }
	},

	// Named variants that override the default, keyed by data-theme.
	themes: {
		dark: {
			palette: { primary: '#2dd4bf', fairway: '#a3e635' }
		}
	}
});

Every run prints a WCAG contrast report over the resolved tokens. It uses the same math and the same pairings that validate this library's own token set. It covers your custom intents too:

$ hyzer generate
config: hyzer.config.ts
wrote src/styles/tokens.css (full, 89 tokens)
contrast: 104 pairings checked, all pass WCAG AA

Aim intents at the middle of a ramp

Ramps are free to define. The report grades text and intent tokens against your surfaces, not raw palette hues, so extra rungs add no pairings on their own. The cost comes when you point an intent at a ramp's end rung. Those rungs are very pale and very dark by design, so neither clears 4.5:1 as text, and the report fails that pairing. --strict then fails the whole run: it is all-or-nothing and cannot be narrowed to certain tokens. Point intents at the middle of a ramp, as brand does above, or run without the flag and read the warnings yourself.

You choose what it writes. The flags compose:

bash
# A complete sheet: import it INSTEAD of tokens.css:
hyzer generate

# A patch sheet with only your overrides: import it AFTER tokens.css:
hyzer generate --mode overrides

# Also write the opt-in utilities sheet, next to the tokens sheet:
hyzer generate --utilities

# Flags compose: a patch sheet AND the utilities sheet, one run:
hyzer generate --mode overrides --utilities

# Validate without writing; fail CI on any AA miss (and any unknown icon):
hyzer generate --check --strict

TypeScript configs need Node 22.18

They load through Node's native type stripping. On older runtimes, name the file hyzer.config.mjs instead. You can also import the engine straight from @hyzer-labs/ui/config (resolveConfig, generateCss, contrastReport) for build scripts of your own.

Command-line flags

The flags and the config file cover different ground, on purpose. Three flags have no config equivalent at all, because they describe a single run rather than your design system. Most of the config (tokens, themes, icons) has no flag, because none of it belongs on a command line.

FlagConfig keyWhat it does
--config <path>Which config file to read. Defaults to hyzer.config.ts, .js or .mjs in the current directory.
--out <path>outputWhere the token sheet is written. The flag wins over the config key. Set neither and it goes to ./hyzer-tokens.css. The utilities sheet follows it, unless utilities.output names a path of its own.
--mode <mode>"full" (the default) writes a complete sheet that replaces tokens.css. "overrides" writes a patch sheet to import after it.
--utilitiesutilitiesAlso write the utilities sheet. It turns the sheet on even when the config does not. A path set in the config is still used.
--checkResolve and report without writing any files: no token sheet, no utilities sheet, no icons.ts. Pairs with --strict for a CI check that touches nothing.
--strictExit non-zero if any pairing misses WCAG AA or any icon name is unknown. Without it, both are warnings and the run succeeds. All-or-nothing: it cannot be narrowed to one pairing.
--helpPrint the usage summary this table is drawn from.

Trim the icon set

The config reaches icons too: an optional icons: string[] list of kebab-case Lucide names. hyzer generate emits an icons.ts module next to the tokens sheet, with named re-exports from the @hyzer-labs/ui/icons/<name> deep paths. It covers your list plus the library's always-shipped core set (the chevrons, close, menu, and friends its own components depend on). So your app's autocomplete offers your own icon vocabulary rather than the full 1,700-plus Lucide names.

ts hyzer.config.ts
import { defineConfig } from '@hyzer-labs/ui/config';

export default defineConfig({
	// kebab-case Lucide names: 'plus' is already core (deduped, no warning)
	icons: ['plus', 'trash-2', 'settings', 'serch']
});

List a core icon explicitly and it is deduplicated into the core group with no warning. An unknown name is a report warning by default; --strict turns it into a failing run. Either way the icon is left out of the emitted barrel. Omit the icons key and hyzer generate writes no icons.ts at all, and skips that report section. icons: [] behaves differently: the empty array is a valid, minimal config that does write the file, holding the core set on its own.

$ hyzer generate
wrote hyzer-tokens.css (full, 84 tokens)
wrote icons.ts (16 icons)
contrast: 92 pairings checked, all pass WCAG AA
  ? icons: "serch" is not a valid Lucide icon name, omitted from the barrel
icons: 1 unknown name(s) (warnings; use --strict to fail the build)
icons: 16 included (14 core, 2 configured)

Generate the utilities sheet

The opt-in utilities sheet is engine output too. Set utilities: true in the config and hyzer generate writes hyzer-utilities.css next to the tokens sheet. The object form, utilities: { output: '...' }, picks a custom path. Leave the key out (the default) and you get no utilities file.

--utilities on the command line does the same. It turns the sheet on even when the config does not. A path you set in the config is still used.

ts hyzer.config.ts
import { defineConfig } from '@hyzer-labs/ui/config';

export default defineConfig({
	// true opts in with the default filename; { output } picks a custom path
	utilities: true
});
$ hyzer generate
config: hyzer.config.ts
wrote hyzer-tokens.css (full, 84 tokens)
wrote hyzer-utilities.css
contrast: 92 pairings checked, all pass WCAG AA

Full config reference

Every group hyzer.config.ts accepts, in one file and commented out. Uncomment what you need and delete the rest. Each line's comment names the tokens it drives.

ts hyzer.config.ts
import { defineConfig } from '@hyzer-labs/ui/config';

export default defineConfig({
	// output: 'src/styles/tokens.css', // where `hyzer generate` writes the sheet

	// tokens: {                            // the DEFAULT theme (the :root block)
	// 	palette: {                          // raw hues (--hz-palette-*); ramps welcome
	// 		primary: '#0f766e',
	// 		brandRed: { 500: '#ef4444', 900: '#7f1d1d' }
	// 	},
	// 	color: { border: '#94a3b8' },       // structural role tokens (--hz-color-*)
	// 	intent: { fairway: 'var(--hz-palette-primary)' }, // remap or add intents (--hz-intent-*)
	// 	space: { xs: '0.375rem' },          // the fixed margin/gap scale (--hz-space-*)
	// 	width: { md: '960px' },             // layout max-widths (--hz-width-*)
	// 	typography: {
	// 		fontSize: { base: '1.05rem' },    // --hz-font-size-*
	// 		fontFamily: { sans: "'Inter', system-ui, sans-serif" }, // --hz-font-family-*
	// 		fontWeight: { semibold: '650' },  // --hz-font-weight-*
	// 		lineHeight: { base: '1.6' }       // --hz-line-height-*
	// 	},
	// 	radius: { md: '0.625rem' },         // corner radii (--hz-radius-*)
	// 	border: { width: { thin: '1.5px' } }, // border widths (--hz-border-width-*)
	// 	shadow: { md: '0 10px 15px -3px rgb(0 0 0 / 0.15)' }, // elevation (--hz-shadow-*)
	// 	zIndex: { modal: '1200' },          // stacking order (--hz-z-*)
	// 	motion: {
	// 		duration: { base: '350ms' },      // --hz-duration-*
	// 		ease: { standard: 'ease-out' }    // --hz-ease-*
	// 	},
	// 	density: { unit: '0.5rem' }         // the --hz-density grid unit (near/away cascade)
	// },

	// themes: {                            // variants that override the default,
	//                                     // one block per data-theme="<name>"
	// 	dark: {                            // [data-theme="dark"]
	// 		palette: { primary: '#2dd4bf' },  // hue overrides for dark
	// 		color: { surface: '#020617' },    // role overrides for dark
	// 		intent: { fairway: '#a3e635' }    // intent remaps for dark only
	// 	},
	// 	ocean: { palette: { primary: '#0ea5e9' } } // any name you like
	// },

	// icons: ['plus', 'trash-2', 'settings'], // trims the generated icons.ts barrel

	// utilities: true // opt in to hyzer-utilities.css (or { output: 'styles/hyzer-utilities.css' })
});

Where to go next

Tokens & Overrides

The two-layer token model the generated sheet writes, and how to reach it in plain CSS.

Contrast & Accessibility

The report's math, as live ratios you can read off the page.

Example themes

Complete configs you can copy, from a token-only theme to one built from scratch.