Primitives
The raw values: hex values, rem steps, font stacks, curve definitions. Nothing on the site references these directly except the semantic layer; if a component needs one, it asks through an alias.
Colour , Direction A (warm)
Both palettes ship. The Warm / Cool switch in the navigation flips the whole site between them, and every semantic token resolves against whichever set is live.
paper
-
--paper-50#fcfaf5 -
--paper-100#f7f4ed
cream
-
--cream-100#f0ede5
ink
-
--ink-875#21201a -
--ink-900#1c1b17 -
--ink-925#1b1a16 -
--ink-950#14130f
grey
-
--grey-300#8c8a82 -
--grey-400#ada89b -
--grey-500#6b675c -
--grey-600#5c5950 -
--grey-700#4a4842
line
-
--line-100#e7e2d6 -
--line-200#d9d2c0 -
--line-700#3e3b31 -
--line-800#2b2922
rust
-
--rust-400#c97a3f -
--rust-600#8c4d1f
Colour , Direction B (cool)
chalk
-
--chalk-50#fafaf8 -
--chalk-100#f4f4f2
frost
-
--frost-100#ececec
carbon
-
--carbon-875#1c1c1e -
--carbon-900#161616 -
--carbon-925#161617 -
--carbon-950#0f0f10
steel
-
--steel-300#888888 -
--steel-400#ababab -
--steel-500#676767 -
--steel-600#5a5a5a -
--steel-700#484848
seam
-
--seam-100#e4e4e2 -
--seam-200#d2d2cf -
--seam-700#3c3c3f -
--seam-800#28282a
cobalt
-
--cobalt-400#6b8cff -
--cobalt-600#2a4abd
Spacing scale
Nine steps, each drawn at true size. The semantic taxonomy assigns these to directions; the scale itself carries no meaning about where a gap goes.
-
--space-10.25rem , 4px -
--space-20.5rem , 8px -
--space-30.75rem , 12px -
--space-41rem , 16px -
--space-61.5rem , 24px -
--space-82rem , 32px -
--space-123rem , 48px -
--space-164rem , 64px -
--space-246rem , 96px
Type families
-
--font-sansHanken Grotesk Variable Hanken Grotesk, the workhorse voice -
--font-serifSeason Serif Season Serif, the licensed display face, headlines -
--font-mono-uiJetBrains Mono Variable JetBrains Mono: eyebrows, labels, table headers, key caps -
--font-mono-dataJetBrains Mono Variable JetBrains Mono: numerals, dates, token names, hex, code
Type sizes
-
--text-displayclamp(2.25rem, 3.7vw, 4.25rem) , 36–68px -
--text-headingclamp(1.875rem, 3vw, 3rem) , 30–48px -
--text-bodyclamp(1.125rem, 0.75rem + 0.4167vw, 1.25rem) , 18–20px -
--text-smallclamp(0.9375rem, 0.75rem + 0.2083vw, 1rem) , 15–16px -
--text-captionclamp(0.8125rem, 0.625rem + 0.2083vw, 0.875rem) , 13–14px
Weights
-
--weight-body380 body, lead, small, meta -
--weight-label400 mono labels, captions, table headers -
--weight-heading400 serif headings (h1–h3) -
--weight-action450 buttons, segmented controls, metric numerals, list item names -
--weight-strong550 wordmark
Leading
-
--leading-tight1.08 page headline (H1) -
--leading-snug1.12 headings (H2, H3), list item names -
--leading-relaxed1.6 body, lead, small
Tracking
-
--tracking-tight-0.01em wordmark -
--tracking-heading0.02em serif headings (h1–h3) -
--tracking-ui0.04em sentence-case mono in the controls -
--tracking-wide0.08em mono labels and table headers
Border widths
-
--border-width-sm1px -
--border-width-md2px -
--border-width-lg3px
Radii
-
--radius-sm2px -
--radius-md4px -
--radius-pill999px fully rounded ends: pills, scrollbar track and thumb
Motion
Hover or focus a row and the dot crosses its track at that token’s duration, on the standard curve. The Reduce motion switch (and the OS preference) stills them.
-
--motion-fast150ms hovers, small state changes -
--motion-standard300ms panels, disclosure -
--motion-slow450ms large moves, page-level shifts -
--motion-press80ms the press, and only the press: under --motion-fast so it lands under the finger -
--ease-standardcubic-bezier(0.25, 0.1, 0.25, 1) the one easing curve, everywhere
Press
How far a control travels when you hold it down. Press and hold the square. The matching
duration is --motion-press in the list above.
-
--press-scale0.96 buttons, segments, icon buttons: anything thumb-sized
Density
--density is a unitless multiplier: 1 by default and
0.7 when the Compact switch in the navigation is on. Every semantic spacing token runs
through it, which is why the whole page tightens with one switch. Flip it and watch the
spacing bars in the Semantics view resize.
Semantics
Semantic tokens give components names they can rely on, like text-strong,
instead of raw values. Change the theme and the tokens change with it, without changing the
components.
Colour
Swatches use the live custom properties, so they always reflect the active theme and palette. The token name updates with them. Use the controls in the navigation to switch modes and see the colours change.
-
--color-surface-page→ --paper-100 -
--color-surface-raised→ --paper-50 -
--color-surface-band→ --cream-100 -
--color-text-strong→ --ink-900 -
--color-text-default→ --grey-700 -
--color-text-subtle→ --grey-500 -
--color-link-underline→ --grey-400 -
--color-border-strong→ --grey-500 -
--color-border-default→ --line-200 -
--color-border-subtle→ --line-100 -
--color-accent-default→ --rust-600 -
--color-focus-ring→ --rust-600
Spacing
Six concepts replace a flat spacing scale, in two families. inset is space
inside a box and compiles to padding; gap is space between boxes and compiles
to gap. Each family has a both-axis token and two axis-specific ones. Every token uses the
primitive scale and responds to the density setting, so the bars below show the actual size.
Read more about the approach on spacing tokens.
inset internal, both axes. Card padding.
-
--spacing-inset-xs= --space-1 × density -
--spacing-inset-sm= --space-2 × density -
--spacing-inset-md= --space-4 × density -
--spacing-inset-lg= --space-6 × density
inset-block internal, vertical. Content spacing inside a panel.
-
--spacing-inset-block-xs= --space-1 × density -
--spacing-inset-block-sm= --space-2 × density -
--spacing-inset-block-md= --space-3 × density -
--spacing-inset-block-lg= --space-6 × density -
--spacing-inset-block-xl= --space-12 × density
inset-inline internal, horizontal. Left and right padding in buttons.
-
--spacing-inset-inline-xs= --space-2 × density -
--spacing-inset-inline-sm= --space-3 × density -
--spacing-inset-inline-md= --space-4 × density -
--spacing-inset-inline-lg= --space-8 × density
gap external, both axes. The grid and flex gap shorthand.
-
--spacing-gap-xs= --space-1 × density -
--spacing-gap-sm= --space-2 × density -
--spacing-gap-md= --space-4 × density -
--spacing-gap-lg= --space-8 × density -
--spacing-gap-xl= --space-16 × density -
--spacing-gap-2xl= --space-24 × density
gap-block external, vertical. Rows, stacked sections, list items.
-
--spacing-gap-block-xs= --space-1 × density -
--spacing-gap-block-sm= --space-2 × density -
--spacing-gap-block-md= --space-4 × density -
--spacing-gap-block-lg= --space-8 × density -
--spacing-gap-block-xl= --space-16 × density -
--spacing-gap-block-2xl= --space-24 × density
gap-inline external, horizontal. Gaps between adjacent controls.
-
--spacing-gap-inline-xs= --space-2 × density -
--spacing-gap-inline-sm= --space-3 × density -
--spacing-gap-inline-md= --space-6 × density -
--spacing-gap-inline-lg= --space-12 × density
Text styles
Each type role in type.css combines the font family, size, weight, line height,
letter spacing and colour into one class, much like a Figma text style. The examples use the
actual type styles, so you can see exactly what each one looks like. Apply a
.type-* class directly, or use one of the combined selectors.
Page headline
Section heading
Subheading
Name , list item title
Lead / intro
Body
Detail / UI text
Eyebrow / label
Meta / mono data
Metric
Wordmark
Interaction states
Four states, each carried by a different property, so they can't be confused. Rest uses the
component's own colour. Hover uses the accent (--color-accent-default) because
here it signals a change of state. Focus is the ring, and nothing else. Pressed is
geometry: the control scales inward on --press-scale, over
--motion-press.
Two things don't scale. Links and text-only controls have no box to scale, and moving text reads as a rendering fault, so they keep the underline and accent on hover. Cards have the opposite problem: even a small scale reads as the page reflowing, so they use a directional cue instead.
Reduced motion keeps the press but removes the animation. The control still responds; it just arrives rather than travelling. Less movement doesn't mean less feedback.
Every control below is the real one, out of global.css. Press and hold any of
them. The segmented control and the switch are the exceptions, and the table says why.
Every pressable on the site uses the same mechanism. It lives in one place: the
pressable class in global.css. Components use that class unless
they need different timing, in which case they use the same two lines and the same tokens.
| Control | Hover | Pressed |
|---|---|---|
Button .button | Fills accent, text drops to the page surface. | Scales to --press-scale. |
Segmented control .seg button | The unselected segments take the accent as text. Never the raised surface with strong text: that was the selected state's own two colours, swapped. | Nothing, deliberately. A segment sits inside a shared border, so scaling it opens a gap between its fill and the frame. And the selection lands on the same event. By the time your finger is up the segment has inverted and the page has changed, so a press state would be decoration in front of feedback that has already arrived. |
Toggle switch .wt-switch button | Off, the border firms to the strong neutral and the knob darkens with it, not the accent, which on this control already means 'on'. On, that same neutral ring is invisible against the fill (1.2:1 in light mode), so the pill itself deepens instead, mixed off the accent toward the text colour so it holds in every theme. Either way the knob doesn't move: on a switch, movement means the state changing, and hover hasn't changed anything. | The knob leans 0.2rem toward the end the release will send it to, on --motion-press. Not a scale: a squash on something this small read as a third state. The lean is the knob doing early what it is about to finish, so it completes the travel from 0.2rem further along, on its own slower ramp. |
Card link .cs-pager__next | Border warms to accent. | The arrow goes right. A card is too wide to scale usefully, so the press moves the cue it already carries, in the direction the link goes: out on --motion-press, drifting back on the resting ramp, so a tap pulses. |
Carousel arrow .gallery-arrow | Border firms to --color-text-strong. | Scales to --press-scale, and takes the same border. On touch there is no hover to have fired first. |
Carousel dot .gallery-dot | None. | Grows half way toward the pill it becomes when selected. The press is the first half of the state change: let go and it finishes, move away and it goes back. A 10px dot cannot usefully scale by 3%. |