Docs

Theming

Five independent axes, set as attributes. Change any of them on <html> or on a single section.

The five axes

AxisAttributeValues
Themeclasslight · dark
Materialdata-materialsolid · glass
Accentdata-accentlime · ocean · ember · rose · graphite (or your own)
Densitydata-densitycomfortable · compact
Fontdata-fonteditorial · grotesk · corporate · system

Every demo below is the same markup. Only the attribute on the wrapper changes.

Theme

class="light"

Q4 plan

Live
class="dark"

Q4 plan

Live

Material

Glass makes surfaces translucent with a backdrop blur. At most three blurred surfaces are on screen at once; nested layers stay opaque.

data-material="solid"

Q4 plan

Live
data-material="glass"

Q4 plan

Live

Accent

One source colour in OKLCH. Soft fills, readable text and the focus ring are derived from it.

data-accent="lime"

Q4 plan

Live
data-accent="ocean"

Q4 plan

Live
data-accent="ember"

Q4 plan

Live
data-accent="rose"

Q4 plan

Live
data-accent="graphite"

Q4 plan

Live

Density

Changes --radius, --control-h and --gap. Compact suits data-heavy screens.

data-density="comfortable"

Q4 plan

Live
data-density="compact"

Q4 plan

Live

Font

PresetUIHeadingsNumbers
editorialOnestInstrument SerifJetBrains Mono
groteskHanken GroteskBricolage GrotesqueGeist Mono
corporateIBM Plex SansIBM Plex SansIBM Plex Mono
systemSystem UISystem UISystem mono

Heading weight, tracking, size scale and the style of <em> change with the preset.

data-font="editorial"

Q4 plan

Live
data-font="grotesk"

Q4 plan

Live
data-font="corporate"

Q4 plan

Live
data-font="system"

Q4 plan

Live

Token reference

Components read only these semantic tokens. Each is available as a Tailwind colour, e.g. bg-surface, text-fg-muted, border-line.

TokenUsed for
scenePage background behind every surface
fg / fg-muted / fg-subtleText: primary, secondary, tertiary
surface / surface-2 / surface-borderCard surface, nested tile, card border
lineHairlines and dividers
field / field-border / field-border-hoverInputs and other controls
hover / selectedInteractive row and item states
skeleton / trackLoading placeholders, slider and progress tracks
popover / popover-border / overlayFloating layers and the dialog backdrop
nav / nav-fg / nav-fg-strong / nav-hover / nav-active / nav-line / nav-subSidebar (dark in both themes)
nav-popover / nav-popover-borderSidebar flyouts
accent / accent-fg / accent-soft / accent-inkAccent fill, text on accent, tinted background, readable accent text
ringFocus ring
success / warning / danger / infoSemantic status colours
chart-1 … chart-6Chart series

Custom accent

Skip the presets and set the four source values. Everything else is derived.

:root {
  --accent-base: oklch(0.72 0.16 160);      /* fill */
  --accent-fg: oklch(0.18 0.02 160);        /* text on the fill */
  --accent-ink-light: oklch(0.48 0.12 160); /* readable accent text, light theme */
  --accent-ink-dark: oklch(0.82 0.13 160);  /* readable accent text, dark theme */
}

Per-section theming

Axes are inherited, so a section can differ from the page. A dark hero on a light page:

<section className="dark" data-accent="ember">
  <h1>Dinner from 18:00</h1>
  <Button variant="primary">Book a table</Button>
</section>

The one rule

Never write dark: in a component
Read semantic tokens instead. A component that hard-codes dark:bg-zinc-900 breaks every other axis and every section that sets its own theme.