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);
}

Theme

Copy this CSS to your project's theme.css file: