Theming
Design tokens, colour schemes and how to build a skin.
Every colour, size, font and radius in the theme is a CSS custom property defined in css/tokens.css. Change a value there and it updates everywhere it is used, across every page and every format, because nothing in the theme hard-codes a colour or size.
tokens.css is generated from tokens/tokens.json. You can edit either one: edit the CSS directly for a quick change, or edit the JSON and rebuild for a change that regenerates cleanly (and can be shared as a skin, see below).
The one-line rebrand
To move the whole theme to a different brand colour, change these three lines in css/tokens.css:
:root {
--color-brand-primary: #40683C; /* buttons, links, accents, charts */
--color-brand-dark: #2F4A2C; /* gradients, hovers */
--color-brand-accent_interactive: #35583B; /* interactive/hover state */
}
Buttons, links, focus rings, badges, filter pills, the metric numbers, the globe and the molecule all read those variables, so they change together.
Colour
| Variable | Value | Notes |
|---|---|---|
--color-brand-primary | #40683C | 6.44:1 AA |
--color-brand-dark | #2F4A2C | |
--color-brand-secondary | #16233A | |
--color-brand-secondary_2 | #1E3050 | |
--color-brand-accent_interactive | #35583B | 7.9:1 AAA |
--color-brand-accent_spark | #6FA300 | white-text-safe variant, 4.6:1; the raw #93D500 is decorative only |
--color-text-primary | #0F1B2D | 16:1+ |
--color-text-secondary | #475569 | 7.5:1 |
--color-text-tertiary | #64748B | 4.9:1 (meta only) |
--color-text-on_dark | rgba(255,255,255,0.92) | |
--color-text-on_dark_soft | rgba(255,255,255,0.72) | |
--color-bg-page | #FBFCFD | |
--color-bg-subtle | #F4F7FA | |
--color-bg-sunken | #EEF3F7 | |
--color-bg-surface | #FFFFFF | |
--color-border-subtle | #E7EDF3 | |
--color-border-medium | #D3DEE8 | |
--color-border-strong | #B9C7D4 | |
--color-status-info | #0284C7 | |
--color-status-danger | #DC2626 | |
--color-status-danger_bg | #FEF2F2 |
Typography
| Variable | Value | Notes |
|---|---|---|
--type-family_sans | Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif | |
--type-family_mono | 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace | |
--type-scale-xs | 0.75rem | |
--type-scale-sm | 0.875rem | |
--type-scale-base | 1rem | |
--type-scale-lg | 1.125rem | |
--type-scale-xl | 1.375rem | |
--type-scale-2xl | 1.75rem | |
--type-scale-3xl | 2.25rem | |
--type-scale-4xl | 3rem | |
--type-scale-5xl | 3.75rem | |
--type-weight-normal | 400 | |
--type-weight-medium | 500 | |
--type-weight-semibold | 600 | |
--type-weight-bold | 700 | |
--type-leading-tight | 1.15 | |
--type-leading-snug | 1.3 | |
--type-leading-normal | 1.6 |
Spacing
| Variable | Value | Notes |
|---|---|---|
--space-1 | 0.25rem | |
--space-2 | 0.5rem | |
--space-3 | 0.75rem | |
--space-4 | 1rem | |
--space-6 | 1.5rem | |
--space-8 | 2rem | |
--space-12 | 3rem | |
--space-16 | 4rem | |
--space-24 | 6rem | |
--space-32 | 8rem |
Radius
| Variable | Value | Notes |
|---|---|---|
--r-sm | 6px | |
--r-md | 10px | |
--r-lg | 14px | |
--r-xl | 20px | |
--r-pill | 999px |
Shadow
| Variable | Value | Notes |
|---|---|---|
--shadow-xs | 0 1px 2px rgba(16,27,45,0.04) | |
--shadow-sm | 0 1px 3px rgba(16,27,45,0.06), 0 1px 2px rgba(16,27,45,0.04) | |
--shadow-md | 0 4px 12px rgba(16,27,45,0.07), 0 2px 4px rgba(16,27,45,0.04) | |
--shadow-lg | 0 12px 32px rgba(16,27,45,0.10), 0 4px 8px rgba(16,27,45,0.05) | |
--shadow-xl | 0 24px 56px rgba(16,27,45,0.16), 0 8px 16px rgba(16,27,45,0.06) |
Breakpoint
| Variable | Value | Notes |
|---|---|---|
--bp-sm | 640px | |
--bp-md | 768px | |
--bp-lg | 1024px | |
--bp-xl | 1280px |
Dark mode
Dark values live in one block at the bottom of tokens.css, applied when data-theme="dark" is on the root (the toggle sets it; the OS preference is the fallback). Only background, text and border tokens are overridden; brand colours stay the same in both themes.
| Variable | Light | Dark |
|---|---|---|
--color-bg-page | #FBFCFD | #0B1220 |
--color-bg-subtle | #F4F7FA | #111A2B |
--color-bg-sunken | #EEF3F7 | #0E1626 |
--color-bg-surface | #FFFFFF | #131E31 |
--color-text-primary | #0F1B2D | #E8EEF6 |
--color-text-secondary | #475569 | #A9B7C9 |
--color-text-tertiary | #64748B | #7E8DA1 |
--color-border-subtle | #E7EDF3 | #1E2C42 |
--color-border-medium | #D3DEE8 | #26374F |
--color-border-strong | #B9C7D4 | #33465F |
Skins: shipping more than one colourway
A skin is a small JSON file that overrides only the tokens that change. The theme ships tokens/skin-clinical-blue.json, which changes four brand values and nothing else, and produces a complete blue demo. To build it:
python3 build.py --skin tokens/skin-clinical-blue.json --out-tokens tokens-blue.css
That writes dist/css/tokens-blue.css. Point a page at it instead of tokens.css and the whole page is reskinned. To make your own colourway, copy the blue skin, change the values, and build with a new --out-tokens name.