Tokens

Every color a component uses is a semantic role, resolved in one theme block. Components never touch raw values: no hex, no bare gray scales. Change the theme block and the whole product recolors, including the illustrations.

The roles

RoleValueUseUtility
bg#ffffffPage background, set once on the body. Porcelain.bg-bg
surface#ffffffCards, inputs, the floating bar. The layer that sits on bg; borders do the separating.bg-surface
brand#fde047Primary buttons, active states, heading accents. Lemon. Fills only — never text on a light ground.bg-brand / text-brand
brand-hover#eab308Primary hover fill, one step deeper than brand.bg-brand-hover
on-brand#0f172aText and icons on a brand fill. Ink-on-Lemon is the AA pairing.text-on-brand
brand-deep#a16207The brand hue at text strength, for brand-colored text and icons on light grounds.text-brand-deep
accent#f59e0bSecondary accent, the accent button. Amber.bg-accent
accent-hover#d97706Accent hover fill; also the text-safe amber and the focus ring (raw amber misses 3:1 on white).bg-accent-hover
tint#fef9c3Soft brand-tinted fills: badges, selected surfaces.bg-tint
ink#0f172aPrimary text.text-ink
muted#64748bSecondary text, hints, captions.text-muted
line#e5e7ebEvery border. There is exactly one hairline color.border-line
scrollbar-thumbbrandThe scrollbar thumb fill: brand, stepping deeper on hover (brand-hover) and drag (brand-deep). The track is transparent.--scrollbar-thumb …
danger#ef4444Errors only. Functional, not brand — that it equals Tassel Red is a coincidence the once-per-composition rule does not govern.text-danger
warning#d97706Functional status.text-warning
success#16a34aFunctional status.text-success
info#2563ebFunctional status.text-info

Data-viz slots

Charts take categorical color from six fixed slots, assigned in order and never cycled. The set is validated, not eyeballed: every slot sits in the OKLCH readable-lightness band, clears the chroma floor, holds color-vision-deficiency separation from its neighbors, and reaches 3:1 against the surface. A seventh series folds into "Other."

SlotValueNote
chart-1#a16207the brand hue, snapped into the readable band
chart-2#2a78d6blue
chart-3#ef4444Tassel Red
chart-4#008300green
chart-5#4a3aa7violet
chart-6#0d9488teal

Rules

  • Token-driven, always. A component that hardcodes a hex or reaches for a raw gray is a bug, even when the result looks identical.
  • Brand is a fill, not a text color. Lemon text on a light ground is illegible. Text on a brand fill wears on-brand; brand-colored text on a light ground wears brand-deep.
  • Status colors are functional, not brand. Errors are danger-red even in a lemon product; warnings, success, and info keep their conventional hues. Never recolor a status to stay on-brand.
  • One hairline. All borders share the single line token. If a border needs to be darker, the component actually wants a background change (see Surfaces).
  • Utilities read as roles. Markup says bg-surface and border-line, never a value.
  • There is no dark mode. One light theme. This is a stance, not an omission.
  • Co-brands re-value roles, never markup. A partner theme (e.g. the Harvard crimson instance) overrides the theme block under its instance selector; component markup never branches on instance.