Theming
Customise ZUI's appearance by overriding the custom properties defined in theme.css.
How it works
ZUI ships with a default theme.css that maps semantic tokens to the underlying colour palettes, fonts, and radii. To customise, create your own CSS file that's imported after ZUI's stylesheet and override the :root custom properties.
/* Import ZUI first */
@import '@mrmartineau/zui/css';
/* Then override in your own stylesheet */
:root {
--color-theme: light-dark(var(--color-violet-600), var(--color-violet-400));
--custom-font-sans: 'IBM Plex Sans';
--radius-scale: 1.5;
}
Because ZUI uses @layer, your unlayered overrides will always win regardless of source order. Alternatively, place overrides inside the zui.base layer.
Default theme
Here is the full default theme.css for reference:
@layer zui.base {
:root {
/* Semantic colours */
--color-theme: light-dark(var(--color-mist-600), var(--color-mist-400));
--color-accent: light-dark(var(--color-sky-600), var(--color-sky-400));
--color-background: light-dark(
var(--color-mist-200),
var(--color-mist-800)
);
--color-surface: light-dark(var(--color-mist-200), var(--color-mist-800));
--color-border: light-dark(var(--color-mist-300), var(--color-mist-700));
--color-text: light-dark(var(--color-mist-700), var(--color-mist-300));
--color-success: light-dark(
var(--color-emerald-600),
var(--color-emerald-400)
);
--color-error: light-dark(var(--color-red-600), var(--color-red-400));
--color-warning: light-dark(var(--color-amber-600), var(--color-amber-400));
/* Fonts */
--custom-font-sans: Geist, Inter;
--custom-font-serif: Times New Roman;
--custom-font-mono: Geist Mono;
--font-sans: var(--custom-font-sans), var(--font-stack-sans);
--font-serif: var(--custom-font-serif), var(--font-stack-serif);
--font-mono: var(--custom-font-mono), var(--font-stack-mono);
--font-headings: var(--font-serif);
--font-body: var(--font-sans);
--line-height: var(--line-height-md);
/* Shape */
--radius-scale: 1;
--border-style: solid;
}
}
Semantic colours
These tokens are used throughout all components. Each uses light-dark() to automatically adapt to the user's colour scheme.
--color-theme Main theme colour used by buttons, links, focus rings, and active states. --color-accent Accent colour for alternative actions and highlights. --color-background Page background colour. --color-surface Elevated surface colour for cards, sidebars, and panels. --color-border Default border colour for inputs, cards, and dividers. --color-text Default text colour. --color-success Positive feedback — confirmations, success states. --color-error Negative feedback — errors, destructive actions, invalid inputs. --color-warning Cautionary feedback — warnings, attention needed. Example: changing the theme colour
:root {
--color-theme: light-dark(var(--color-violet-600), var(--color-violet-400));
}
You can use any of the 26 built-in colour palettes: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose, mist, mauve, olive, taupe. Each palette has shades from 50 to 950.
Or use any raw colour value:
:root {
--color-theme: light-dark(oklch(0.55 0.2 270), oklch(0.75 0.15 270));
}
Shorthand: derive light/dark from a single colour
Because ZUI's palette is defined in OKLCH, you can use CSS relative colour syntax to derive both light and dark variants from a single base colour. The idea is to fix the lightness for each mode and let the hue and chroma carry through from whatever colour you supply:
:root {
--color-theme-base: var(--color-violet-500);
--color-theme: light-dark(
oklch(from var(--color-theme-base) 0.46 c h),
oklch(from var(--color-theme-base) 0.72 c h)
);
}
The fixed lightness values (0.46 for light mode, 0.72 for dark mode) approximate the -600/-400 weight split used by the defaults. You can tune them to taste.
This works with any valid colour — a palette variable, a raw oklch(), or even a hex value:
:root {
--color-theme: light-dark(
oklch(from #7c3aed 0.46 c h),
oklch(from #7c3aed 0.72 c h)
);
--color-accent: light-dark(
oklch(from var(--color-sky-500) 0.46 c h),
oklch(from var(--color-sky-500) 0.72 c h)
);
}
Things to be aware of: OKLCH chroma is not linear across lightness, so very saturated or unusual hues (e.g. yellow, lime) may need slightly adjusted lightness targets to feel visually balanced. Full manual light-dark(…) overrides are always available when you need precise control.
Fonts
Override the --custom-font-* properties to change typefaces. ZUI appends robust fallback stacks automatically.
:root {
--custom-font-sans: 'IBM Plex Sans';
--custom-font-serif: 'Playfair Display';
--custom-font-mono: 'JetBrains Mono';
}
You can also control which font stack is used for headings and body text independently:
:root {
/* Use serif for headings, sans for body */
--font-headings: var(--font-serif);
--font-body: var(--font-sans);
}
Border radius
All radii in ZUI are multiplied by --radius-scale. Change this single value to globally adjust roundness.
:root {
/* Sharper corners */
--radius-scale: 0.5;
/* Rounder corners */
--radius-scale: 2;
/* No rounding at all */
--radius-scale: 0;
}
Border style
The --border-style token controls the border style used by all components. Defaults to solid.
:root {
--border-style: dashed;
}
Line height
The base body line height is controlled by --line-height. It defaults to --line-height-md (1.5).
:root {
/* Tighter body text */
--line-height: var(--line-height-sm);
/* More generous spacing */
--line-height: var(--line-height-lg);
}
Full example
A complete theme override combining colours, fonts, and shape:
@import '@mrmartineau/zui/css';
:root {
/* Colours — violet theme, slate accent */
--color-theme: light-dark(var(--color-violet-600), var(--color-violet-400));
--color-accent: light-dark(var(--color-slate-600), var(--color-slate-400));
--color-background: light-dark(
var(--color-stone-100),
var(--color-stone-900)
);
--color-surface: light-dark(var(--color-stone-50), var(--color-stone-800));
--color-border: light-dark(var(--color-stone-300), var(--color-stone-700));
--color-text: light-dark(var(--color-stone-800), var(--color-stone-200));
/* Fonts */
--custom-font-sans: 'Inter';
--custom-font-mono: 'Fira Code';
--font-headings: var(--font-sans);
/* Shape */
--radius-scale: 1.5;
--border-style: solid;
/* Line height */
--line-height: var(--line-height-md);
}