Theming

Every colour in the library resolves through one token layer. That is what makes the toggle in the header work without a single dark: class, and it is why re-theming is a handful of CSS declarations rather than a fork.

How it works

Components style themselves with semantic utilities — bg-surface, text-foreground-muted, border-error-muted — which Tailwind resolves to custom properties declared in @xui/core/styles/theme.css. Swap the values and every component follows.

Never reach for the raw palette

bg-white and text-zinc-900 cannot follow the active theme. If you find yourself writing a dark: variant, that is the signal a raw colour crept in — there is a token for it.

Switching themes

Put .dark or [data-theme='dark'] on <html>. With neither present the theme follows prefers-color-scheme. Both selectors work on any element, so a subtree can render in the opposite theme — useful for a preview pane.

<html class="dark"></html>

<!-- or, on any subtree -->
<div data-theme="light"></div>

To avoid a flash of the wrong theme, decide before first paint rather than at bootstrap. This site uses exactly this:

// index.html, before the app bundle
const stored = localStorage.getItem('xui-docs-theme');
const dark = stored ? stored === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.add(dark ? 'dark' : 'light');

Re-theming

Override tokens after the import. The darker, lighter, subtle, muted and emphasis steps of each intent are derived with oklch() and color-mix(), so one declaration re-colours the whole ramp — hover, border, badge and callout included.

@import 'tailwindcss';
@import '@xui/core/styles/theme.css';

:root {
  --primary: var(--color-violet-600);
  --surface-inset: var(--color-zinc-50);
}

Do it with the swatches instead

The theme builder edits these same tokens with a colour picker, re-themes this whole site as you go, checks the contrast of every pair that has to stay legible, and hands back exactly this block of CSS.

Control density

Anything you click or type into takes its height and horizontal padding from one scale, so a button, an input and a date field on the same row line up. Override the middle step and the library re-tunes with it.

:root {
  --control-height-sm: 1.5rem;    /* 24px */
  --control-height-md: 1.875rem;  /* 30px — the default step */
  --control-height-lg: 2.5rem;    /* 40px */

  --control-padding-sm: 0.5rem;
  --control-padding-md: 0.75rem;
  --control-padding-lg: 1rem;
}

Token reference

89 tokens, read out of theme.css at build time. Swatches render in whichever theme you are viewing.

Surfaces

Backgrounds, in depth order. `background` is the page, `surface` sits on it, `surface-raised` on that.

ColourTokenUtility--backgroundcolor--mutedcolor--muted-foregroundcolor--surfacecolor--surface-insetcolor--surface-overlaycolor--surface-raisedcolor--surface-sunkencolor

Text

Foreground colours by emphasis, not by shade.

ColourTokenUtility--foregroundcolor--foreground-mutedcolor--foreground-on-emphasiscolor--foreground-subtlecolor

Borders

Divider and outline colours.

ColourTokenUtility--bordercolor--border-mutedcolor--border-strongcolor

Intents

The six meanings. Each has a derived ramp, so re-colouring one declaration re-colours all of it.

ColourTokenUtility--errorcolor--error-darkerderivedcolor--error-emphasisderivedcolor--error-foregroundcolor--error-lighterderivedcolor--error-mutedderivedcolor--error-subtlederivedcolor--infocolor--info-darkerderivedcolor--info-emphasisderivedcolor--info-foregroundcolor--info-lighterderivedcolor--info-mutedderivedcolor--info-subtlederivedcolor--primarycolor--primary-darkerderivedcolor--primary-emphasisderivedcolor--primary-foregroundcolor--primary-lighterderivedcolor--primary-mutedderivedcolor--primary-subtlederivedcolor--secondarycolor--secondary-darkerderivedcolor--secondary-emphasisderivedcolor--secondary-foregroundcolor--secondary-lighterderivedcolor--secondary-mutedderivedcolor--secondary-subtlederivedcolor--successcolor--success-darkerderivedcolor--success-emphasisderivedcolor--success-foregroundcolor--success-lighterderivedcolor--success-mutedderivedcolor--success-subtlederivedcolor--warningcolor--warning-darkerderivedcolor--warning-emphasisderivedcolor--warning-foregroundcolor--warning-lighterderivedcolor--warning-mutedderivedcolor--warning-subtlederivedcolor

State

Focus, selection and the hover/active overlays, so interactive states look the same everywhere.

ColourTokenUtility--active-overlaycolor--focuscolor--hover-overlaycolor--linkcolor--link-hovercolor--selectioncolor

Elevation

The shadow scale. Separate from Tailwind’s `shadow-*` so re-tuning depth never moves `shadow-md`.

TokenUtility--elevation-0shadow--elevation-1shadow--elevation-2shadow--elevation-3shadow--elevation-4shadow--elevation-overlayshadow

Motion

Durations and easings.

TokenUtility--duration-base--duration-fast--duration-slow

Other

Everything else the theme declares.

TokenUtility--chart-1color--chart-2color--chart-3color--chart-4color--chart-5color--chart-6color--chart-7color--chart-8color--control-height-lg--control-height-md--control-height-sm--control-padding-lg--control-padding-md--control-padding-sm--emphasis-l-max--emphasis-l-min--emphasis-shift

xUI 2.0.0 — Apache 2.0 licensed. Built with Angular and Tailwind CSS.