CSS is Awesome

Design tokens

Every visual decision in the system lives in one file: theme.css. Swap that file and the entire UI reskins — buttons, cards, shadows, type, spacing — without touching a line of component CSS.

How tokens work

Tokens are plain CSS custom properties declared on :root. Components read them with var(--token), so a single stylesheet swap cascades everywhere. No build step is required to consume them — drop the file in, and the browser does the rest.

/* theme.css — the one file you swap */
:root {
  --paper:         #F7F3EA;
  --ink:           #2A241E;
  --ai:            #1F3A5F;
  --radius-md:     3px;
  --shadow-md:     0 4px 18px rgba(42,36,30,.08);
}

/* any consumer — base, component, or your own CSS */
.button {
  background: var(--paper-raised);
  color:      var(--ink);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

Palette

Color tokens split into three intents: neutrals (paper & ink), brand (indigo, vermilion, ochre), and status (success / warning / error / info). Every swatch below reads live from the active theme — pick a new theme and the whole grid reskins.

Neutrals
--paper
--paper-raised
--paper-sunk
--paper-glass
--ink
--ink-soft
--ink-faint
--graphite
--muted
--hair
--hair-soft
--guide
--guide-soft
Brand
--brand-primary
--brand-primary-hover
--ai
--ai-ink
--ai-wash
--shu
--shu-wash
--ochre
--ochre-wash
Status
--success-default
--success-subtle
--success-text
--warning-default
--warning-subtle
--warning-text
--error-default
--error-subtle
--error-text
--info-default
--info-subtle
--info-text
Surfaces
--surface-default
--surface-raised
--surface-sunk
--surface-subtle
--surface-muted
--surface-emphasis
--surface-glass
/* Consume any color token the same way */
.card {
  background: var(--surface-raised);
  color:      var(--text-primary);
  border:     1px solid var(--border-default);
}

Typography

Four font stacks cover the whole system: a chunky display serif, a reading serif, a handwritten script for accents, and a mono for code. A sans stack anchors the body. Size, weight, and line-height live in their own slots so themes can retune the whole rhythm.

--font-displayDraft & revise.
--font-serifReading text sits in a warm serif, patient and honest.
--font-sans / --font-primaryThe default body font — crisp, quiet, does its job.
--font-scripthandwritten marginalia
--font-monoconst paper = '#F7F3EA';
body {
  font-family: var(--font-primary);
  font-size:   var(--font-size-base);
  line-height: var(--line-height-normal);
}

h1 { font-family: var(--font-display); }
code { font-family: var(--font-mono); }

Type scale

Themes declare --font-size-base, --line-height-normal, and --font-weight-medium as the canonical hooks. Scale steps are computed from the base so the whole system stays proportional.

Spacing

A six-step t-shirt scale drives every gap, padding, and margin in the system. Values are declared in rem so they honor the user's root font size.

--space-2xs
--space-xs
--space-sm
--space-md
--space-lg
--space-xl
.stack > * + * {
  margin-top: var(--space-md);
}
.card { padding: var(--space-lg); }

Radii

Two families ship together: the short --r-* tokens themes use natively, and the longer --radius-* aliases the library mixins consume. They point at the same values.

--radius-sm
--radius-md
--radius-lg
--radius-xl
--radius-full
.button { border-radius: var(--radius-md); }
.avatar { border-radius: var(--radius-full); }

Shadows

Five elevation steps, from a hairline edge to a full modal lift. The Sketchbook theme tunes them as ink bleeding through paper; a glass theme would swap them for frosted bloom with the same token names.

--shadow-sm
--shadow-md
--shadow-lg
--shadow-xl
--shadow-2xl
.modal { box-shadow: var(--shadow-2xl); }
.popover { box-shadow: var(--shadow-lg); }

Transitions

Motion is a theme concern — a paper theme uses a soft ease, a neon theme might snap. Three durations and one easing cover the system.

--duration-fast180ms
--duration-normal240ms
--duration-slow380ms
--easecubic-bezier(.33,.66,.33,1)
.button {
  transition: background var(--duration-fast) var(--ease);
}

The contract

Every theme file must declare the full set of 123 required tokens — even if a given theme sets some of them to neutral values (e.g. --blur-md: none;). That's what guarantees the one-file swap stays lossless: no matter which theme you drop in, the base stylesheet and components always find the slots they read.

The authoritative contract lives in two places: CONTRACT.md (human-readable, grouped and typed) and scripts/theme-contract.json (machine-readable, consumed by the validator). Run the validator against any theme file to confirm it covers the contract:

# validate a theme file against the contract
node scripts/theme-validator.js public/theme.css

Need a starting point? Download any of the built-in starter themes from /docs/install#download, open it up, and change the values. Every token you'll ever need is already slotted in.

Theme