Themes
Pick a look below, or from the picker in the navbar, and this whole site
changes with it: colours, type, spacing, control sizes, corners, shadows
and motion. Each theme is one block of CSS that overrides the ZUI tokens,
applied to <html>. The selected block is printed beside the cards, so
what you read is exactly what is running. Every theme has a light and a
dark mode; the toggle next to the picker flips between them.
Two of the looks are ours. The other six are products you already use, rebuilt from each product's published palette and metrics, so you can see your components under the app they will live in before writing a line of theme CSS.
Spec sheet
The site’s own look. Green ink on warm yellow paper with a red accent, every radius 0, no shadows, quick motion, a regular type scale and a paper sidebar. Dark mode inverts the paper to a deep green and lifts the ink to cream.
.theme-spec {
/* Type */
--font-sans: "Instrument Sans", "Avenir Next", "Segoe UI", sans-serif;
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-base-line-height: 1.6;
--text-lg: 1.125rem;
--text-xl: 1.35rem;
--text-2xl: 2rem;
--text-3xl: 2.75rem;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 600;
/* Density */
--spacing: 0.25rem;
--control-height-sm: 2rem;
--control-height-md: 2.5rem;
--control-height-lg: 2.75rem;
--control-padding-x-sm: 0.6rem;
--control-padding-x-md: 0.75rem;
--control-padding-x-lg: 1rem;
--button-padding-x-sm: 0.75rem;
--button-padding-x-md: 1rem;
--button-padding-x-lg: 1.5rem;
/* Shape and depth */
--radius: 0;
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--radius-xl: 0;
--shadow-soft: none;
--shadow-medium: none;
--shadow-large: none;
--shadow-card: none;
--shadow-elevated: 0 0 0 1px rgba(11, 51, 36, 0.18);
--shadow-glow: none;
/* Motion */
--duration-fast: 100ms;
--duration-normal: 150ms;
--duration-slow: 220ms;
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
/* Colour */
--background: #fbf7e9;
--foreground: #0b3324;
--card: #fffdf4;
--card-foreground: #0b3324;
--popover: #fffdf4;
--popover-foreground: #0b3324;
--border: rgba(11, 51, 36, 0.18);
--input: rgba(11, 51, 36, 0.3);
--ring: #008751;
--muted: #f3ecd1;
--muted-foreground: #5a6f62;
--primary: #008751;
--primary-foreground: #fbf7e9;
--primary-text: #006b40;
--secondary: #f3ecd1;
--secondary-foreground: #0b3324;
--accent: #fcd116;
--accent-foreground: #0b3324;
--destructive: #e8112d;
--destructive-foreground: #fbf7e9;
--success: #008751;
--warning: #fcd116;
--warning-foreground: #0b3324;
--gradient-primary: linear-gradient(90deg, #008751 0 50%, #fcd116 50% 100%);
--gradient-hero: linear-gradient(90deg, #008751 0 33%, #fcd116 33% 66%, #e8112d 66% 100%);
--gradient-subtle: linear-gradient(180deg, #fbf7e9, #f3ecd1);
--sidebar-background: #fbf7e9;
--sidebar-foreground: #0b3324;
--sidebar-primary: #008751;
--sidebar-primary-foreground: #fbf7e9;
--sidebar-accent: #f3ecd1;
--sidebar-accent-foreground: #0b3324;
--sidebar-border: rgba(11, 51, 36, 0.18);
}
.dark .theme-spec {
--background: #06211a;
--foreground: #f5e9bd;
--card: #0d3127;
--card-foreground: #f5e9bd;
--popover: #0d3127;
--popover-foreground: #f5e9bd;
--border: rgba(245, 233, 189, 0.18);
--input: rgba(245, 233, 189, 0.3);
--ring: #2fd07f;
--muted: #0d3127;
--muted-foreground: #9db5a2;
--primary: #2fd07f;
--primary-foreground: #06211a;
--primary-text: #6fe3a6;
--secondary: #0d3127;
--secondary-foreground: #f5e9bd;
--accent: #fcd116;
--accent-foreground: #06211a;
--destructive: #ff5468;
--destructive-foreground: #06211a;
--success: #2fd07f;
--shadow-elevated: 0 0 0 1px rgba(245, 233, 189, 0.18);
--gradient-subtle: linear-gradient(180deg, #06211a, #0d3127);
--sidebar-background: #06211a;
--sidebar-foreground: #f5e9bd;
--sidebar-primary: #2fd07f;
--sidebar-primary-foreground: #06211a;
--sidebar-accent: #0d3127;
--sidebar-accent-foreground: #f5e9bd;
--sidebar-border: rgba(245, 233, 189, 0.18);
}
How a theme works
Components read token names (--primary, --radius, --spacing); a
theme gives those names values. Redeclare the values and every component
follows. A theme is a selector plus a set of declarations:
.theme-teal {
--primary: #0f766e;
--primary-foreground: #ffffff;
--radius: 0.25rem;
--font-sans: "Inter", system-ui, sans-serif;
--control-height-md: 2.25rem;
}
.dark .theme-teal {
--primary: #2dd4bf;
--primary-foreground: #042f2e;
}
Where you put the selector decides its reach. On :root or <html> it
themes the whole app; on a class it themes that subtree, which is how a
panel, a preview or an embedded editor can carry its own brand inside a
host (component theming shows this). Dark
mode is the same mechanism: a second set of values under a .dark
ancestor, repeating only what changes. Anything a theme leaves out keeps
the shipped default, so a theme that changes three colours is three
lines.
Two rules keep every theme readable:
- Name the role.
--primaryis the product's principal colour, whatever it is: pink for Bubble, orange for HubSpot, purple for Stripe. - Foregrounds travel with their backgrounds.
--primary-foregroundexists so a theme can make--primaryas dark or as pale as it likes and the text on it stays legible.
What the themes change
| Axis | Tokens | Across the five themes |
|---|---|---|
| Colour | Semantic pairs, surfaces, gradients, sidebar | Spec sheet is green ink on yellow paper; HubSpot pairs an orange action with a navy chrome; Stripe leans on a purple ring; Bubble is pastel pink |
| Type | --font-sans, --font-mono, --text-*, --font-weight-* | Microsoft 365 and Stripe run at 14px; Bubble rounds its face and lifts the line height |
| Spacing | --spacing | One number. Bubble sets it a quarter larger and every gap, padding and table cell breathes |
| Density | --control-height-*, --control-padding-x-*, --button-padding-x-* | A medium control is 1.75rem in Stripe, 2rem in Microsoft 365, 2.5rem in HubSpot |
| Shape | --radius, --radius-sm … --radius-xl | Spec sheet sets every radius to 0; HubSpot stops at 3px, Microsoft 365 at 4px; Bubble takes controls to full pills |
| Depth | --shadow-soft … --shadow-elevated, --shadow-glow | none for Spec sheet; Stripe layers a ring under a drop; Microsoft 365 uses the Fluent ramp; Bubble glows in its own tint |
| Motion | --duration-*, --ease-standard, --ease-out | Fluent's easy-ease for Microsoft 365, slow with overshoot for Bubble |
The structure of the product stays the same across all of them. A new
visual identity costs a few dozen lines of CSS. Start from
theme-template.css, which lists every
token with its default.