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
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.
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 AAAim intents at the middle of a ramp
--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:
# 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 --strictTypeScript configs need Node 22.18
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.
| Flag | Config key | What it does |
|---|---|---|
--config <path> | — | Which config file to read. Defaults to hyzer.config.ts, .js or .mjs in the current directory. |
--out <path> | output | Where 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. |
--utilities | utilities | Also write the utilities sheet. It turns the sheet on even when the config does not. A path set in the config is still used. |
--check | — | Resolve 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. |
--strict | — | Exit 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. |
--help | — | Print 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.
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.
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 AAFull 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.
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' })
});