All tokens
All 144 custom properties Deck declares on
:root, grouped by family. Every preview on this page is the token
applied to something — a background, a width, a shadow, an animation — so what you
are looking at is the value in effect rather than a picture of it.
Using a token
Tokens are plain CSS custom properties. Read one with var() in your own
rules, and set one anywhere in the tree to change it from that element down.
@layer app.pages {
.invoice-total { font-size: var(--text-xl); color: var(--text); }
}
<!-- one tenant, one number, no rebuild -->
<html style="--hue-brand: 265">
<!-- or scoped to a subtree -->
<section class="card" style="--brand-soft: var(--good-100)">
Reach for the semantic roles before the ramps.
--surface resolves to a different step of the neutral ramp in light and
dark; --ink-0 is the same colour in both, and using it directly is how
a component ends up unreadable in dark mode.
| Declared in | Tokens |
|---|---|
src/01-tokens.css |
131 |
src/16-motion.css |
13 |
Hue and chroma
The five numbers the whole palette is generated from. Change one and every shade, border, focus ring and shadow that derives from it follows, with no rebuild and no second stylesheet. This is the retheming story in ten tokens.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--hue-brand |
196 |
01-tokens.css:11 |
|
--hue-accent |
42 |
01-tokens.css:11 |
|
--hue-good |
152 |
01-tokens.css:11 |
|
--hue-warn |
74 |
01-tokens.css:11 |
|
--hue-bad |
24 |
01-tokens.css:11 |
|
--hue-neutral |
232 |
01-tokens.css:11 |
|
--chroma-brand |
— | .118 |
01-tokens.css:11 |
--chroma-accent |
— | .142 |
01-tokens.css:11 |
--chroma-status |
— | .132 |
01-tokens.css:11 |
--chroma-neutral |
— | .012 |
01-tokens.css:11 |
Brand ramp
Eleven steps in OKLCH, computed from --hue-brand and --chroma-brand. The numbers are perceptual lightness, so 500 is the same apparent lightness at every hue — which is what stops a yellow brand from looking washed out where a blue one looks solid.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--brand-50 |
oklch(97.4% calc(var(--chroma-brand) * .16) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-100 |
oklch(94.2% calc(var(--chroma-brand) * .30) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-200 |
oklch(88.6% calc(var(--chroma-brand) * .48) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-300 |
oklch(80.4% calc(var(--chroma-brand) * .70) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-400 |
oklch(70.2% calc(var(--chroma-brand) * .90) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-500 |
oklch(60.8% var(--chroma-brand) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-600 |
oklch(51.6% var(--chroma-brand) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-700 |
oklch(43.4% calc(var(--chroma-brand) * .92) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-800 |
oklch(35.6% calc(var(--chroma-brand) * .78) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-900 |
oklch(28.2% calc(var(--chroma-brand) * .62) var(--hue-brand)) |
01-tokens.css:11 |
|
--brand-950 |
oklch(20.4% calc(var(--chroma-brand) * .46) var(--hue-brand)) |
01-tokens.css:11 |
Neutral ramp
Fifteen steps of near-grey, tinted toward --hue-neutral rather than pure grey. A truly neutral grey beside a saturated brand reads as dirty; a grey carrying a little of the brand hue reads as deliberate.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--ink-0 |
oklch(100% 0 0) |
01-tokens.css:11 |
|
--ink-25 |
oklch(99.1% calc(var(--chroma-neutral) * .5) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-50 |
oklch(97.8% var(--chroma-neutral) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-100 |
oklch(95.4% calc(var(--chroma-neutral) * 1.4) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-200 |
oklch(91.2% calc(var(--chroma-neutral) * 1.8) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-300 |
oklch(84.6% calc(var(--chroma-neutral) * 2.2) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-400 |
oklch(70.8% calc(var(--chroma-neutral) * 2.6) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-500 |
oklch(58.4% calc(var(--chroma-neutral) * 2.8) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-600 |
oklch(47.6% calc(var(--chroma-neutral) * 2.8) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-700 |
oklch(38.2% calc(var(--chroma-neutral) * 2.6) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-800 |
oklch(29.4% calc(var(--chroma-neutral) * 2.4) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-900 |
oklch(22.6% calc(var(--chroma-neutral) * 2.2) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-950 |
oklch(16.2% calc(var(--chroma-neutral) * 2.0) var(--hue-neutral)) |
01-tokens.css:11 |
|
--ink-1000 |
oklch(11.4% calc(var(--chroma-neutral) * 1.8) var(--hue-neutral)) |
01-tokens.css:11 |
Status ramps
Good, warn, bad and accent, at the steps that actually get used: 100 for a fill, 500 for an icon or a border, 600 and 700 for text. Deliberately not a full eleven-step ramp each — the missing steps are ones nothing needed.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--good-100 |
oklch(94% calc(var(--chroma-status) * .3) var(--hue-good)) |
01-tokens.css:11 |
|
--good-500 |
oklch(62% var(--chroma-status) var(--hue-good)) |
01-tokens.css:11 |
|
--good-600 |
oklch(52% var(--chroma-status) var(--hue-good)) |
01-tokens.css:11 |
|
--good-700 |
oklch(44% var(--chroma-status) var(--hue-good)) |
01-tokens.css:11 |
|
--warn-100 |
oklch(95% calc(var(--chroma-status) * .32) var(--hue-warn)) |
01-tokens.css:11 |
|
--warn-500 |
oklch(76% var(--chroma-status) var(--hue-warn)) |
01-tokens.css:11 |
|
--warn-700 |
oklch(50% var(--chroma-status) var(--hue-warn)) |
01-tokens.css:11 |
|
--bad-100 |
oklch(94.5% calc(var(--chroma-status) * .3) var(--hue-bad)) |
01-tokens.css:11 |
|
--bad-500 |
oklch(58% calc(var(--chroma-status) * 1.25) var(--hue-bad)) |
01-tokens.css:11 |
|
--bad-600 |
oklch(52% calc(var(--chroma-status) * 1.25) var(--hue-bad)) |
01-tokens.css:11 |
|
--bad-700 |
oklch(44% calc(var(--chroma-status) * 1.1) var(--hue-bad)) |
01-tokens.css:11 |
|
--accent-100 |
oklch(95% calc(var(--chroma-accent) * .3) var(--hue-accent)) |
01-tokens.css:11 |
|
--accent-500 |
oklch(74% var(--chroma-accent) var(--hue-accent)) |
01-tokens.css:11 |
|
--accent-700 |
oklch(52% calc(var(--chroma-accent) * .9) var(--hue-accent)) |
01-tokens.css:11 |
Semantic roles
The tokens you should actually be reaching for. Each one names a job rather than a colour, and each resolves through light-dark() — which is why the entire dark theme is one attribute rather than a second set of rules. Reference --surface, not --ink-0, and dark mode comes free.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--bg |
light-dark(var(--ink-50), var(--ink-1000)) |
01-tokens.css:11 |
|
--bg-sunken |
light-dark(var(--ink-100), oklch(8.6% .012 var(--hue-neutral))) |
01-tokens.css:11 |
|
--surface |
light-dark(var(--ink-0), var(--ink-950)) |
01-tokens.css:11 |
|
--surface-2 |
light-dark(var(--ink-25), var(--ink-900)) |
01-tokens.css:11 |
|
--surface-hover |
light-dark(var(--ink-50), var(--ink-900)) |
01-tokens.css:11 |
|
--line |
light-dark(var(--ink-200), oklch(28% .016 var(--hue-neutral))) |
01-tokens.css:11 |
|
--line-strong |
light-dark(var(--ink-300), oklch(36% .018 var(--hue-neutral))) |
01-tokens.css:11 |
|
--text |
light-dark(var(--ink-900), var(--ink-100)) |
01-tokens.css:11 |
|
--text-muted |
light-dark(var(--ink-600), var(--ink-400)) |
01-tokens.css:11 |
|
--text-faint |
light-dark(var(--ink-500), var(--ink-500)) |
01-tokens.css:11 |
|
--text-on-brand |
oklch(99% .01 var(--hue-brand)) |
01-tokens.css:11 |
|
--text-on-accent |
oklch(24% .06 var(--hue-accent)) |
01-tokens.css:11 |
|
--text-on-good |
oklch(99% .01 var(--hue-good)) |
01-tokens.css:11 |
|
--text-on-warn |
oklch(24% .06 var(--hue-warn)) |
01-tokens.css:11 |
|
--text-on-bad |
oklch(99% .01 var(--hue-bad)) |
01-tokens.css:11 |
|
--brand |
light-dark(var(--brand-600), var(--brand-400)) |
01-tokens.css:11 |
|
--brand-hover |
light-dark(var(--brand-700), var(--brand-300)) |
01-tokens.css:11 |
|
--brand-soft |
light-dark(var(--brand-50), oklch(26% .05 var(--hue-brand))) |
01-tokens.css:11 |
|
--brand-soft-text |
light-dark(var(--brand-800), var(--brand-200)) |
01-tokens.css:11 |
|
--focus |
light-dark(var(--brand-500), var(--brand-300)) |
01-tokens.css:11 |
|
--scrim |
linear-gradient(to top, oklch(12% .02 var(--hue-neutral) / .82) 0%, oklch(12% .02 var(--hue-neutral) / .62) 22%, oklch(12% .02 var(--hue-neutral) / .28) 48%, transparent 78%) |
01-tokens.css:11 |
Space
A 4px base in rem, plus two fluid steps for page rhythm. Seven of these have utilities; the rest are for your own rules. The scale is explained in full on the spacing reference.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--space-0 |
0 |
01-tokens.css:11 |
|
--space-px |
1px |
01-tokens.css:11 |
|
--space-1 |
.25rem |
01-tokens.css:11 |
|
--space-2 |
.5rem |
01-tokens.css:11 |
|
--space-3 |
.75rem |
01-tokens.css:11 |
|
--space-4 |
1rem |
01-tokens.css:11 |
|
--space-5 |
1.25rem |
01-tokens.css:11 |
|
--space-6 |
1.5rem |
01-tokens.css:11 |
|
--space-8 |
2rem |
01-tokens.css:11 |
|
--space-10 |
2.5rem |
01-tokens.css:11 |
|
--space-12 |
3rem |
01-tokens.css:11 |
|
--space-16 |
4rem |
01-tokens.css:11 |
|
--space-20 |
5rem |
01-tokens.css:11 |
|
--space-24 |
6rem |
01-tokens.css:11 |
|
--space-section |
clamp(2.5rem, 1.4rem + 5.5vw, 6rem) |
01-tokens.css:11 |
|
--space-gutter |
clamp(1rem, .55rem + 2.2vw, 2rem) |
01-tokens.css:11 |
Radius
Five steps in px rather than rem, because a corner is a property of the shape and should not grow when a reader raises their base font size.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--r-xs |
4px |
01-tokens.css:11 |
|
--r-sm |
7px |
01-tokens.css:11 |
|
--r-md |
12px |
01-tokens.css:11 |
|
--r-lg |
18px |
01-tokens.css:11 |
|
--r-full |
999px |
01-tokens.css:11 |
Shadow and ring
Four elevations, an inset highlight, and the focus ring. The shadow colour is tinted toward the neutral hue rather than being black at low alpha, which is what keeps it from going muddy over a coloured surface.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--shadow-color |
— | var(--hue-neutral) |
01-tokens.css:11 |
--shadow-1 |
0 1px 2px oklch(20% .03 var(--shadow-color) / .06), 0 1px 1px oklch(20% .03 var(--shadow-color) / .04) |
01-tokens.css:11 |
|
--shadow-2 |
0 2px 4px oklch(20% .03 var(--shadow-color) / .05), 0 4px 12px oklch(20% .03 var(--shadow-color) / .07) |
01-tokens.css:11 |
|
--shadow-3 |
0 4px 8px oklch(20% .03 var(--shadow-color) / .06), 0 12px 28px oklch(20% .03 var(--shadow-color) / .10) |
01-tokens.css:11 |
|
--shadow-4 |
0 8px 16px oklch(20% .03 var(--shadow-color) / .08), 0 24px 56px oklch(20% .03 var(--shadow-color) / .14) |
01-tokens.css:11 |
|
--shadow-inset |
inset 0 1px 0 oklch(100% 0 0 / .6) |
01-tokens.css:11 |
|
--ring |
0 0 0 3px light-dark(oklch(from var(--focus) l c h / .32), oklch(from var(--focus) l c h / .42)) |
01-tokens.css:11 |
Type
Three families, ten sizes and four line heights. The top five sizes are clamp() expressions, so display type scales with the viewport between a floor and a ceiling instead of stepping at a breakpoint.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--font-sans |
Ag 0123 | ui-sans-serif, system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif |
01-tokens.css:11 |
--font-mono |
Ag 0123 | ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, monospace |
01-tokens.css:11 |
--font-display |
Ag 0123 | var(--font-sans) |
01-tokens.css:11 |
--text-2xs |
.6875rem |
01-tokens.css:11 |
|
--text-xs |
.75rem |
01-tokens.css:11 |
|
--text-sm |
.8125rem |
01-tokens.css:11 |
|
--text-base |
.9375rem |
01-tokens.css:11 |
|
--text-md |
1.0625rem |
01-tokens.css:11 |
|
--text-lg |
clamp(1.125rem, 1.06rem + .32vw, 1.3125rem) |
01-tokens.css:11 |
|
--text-xl |
clamp(1.3125rem, 1.19rem + .62vw, 1.625rem) |
01-tokens.css:11 |
|
--text-2xl |
clamp(1.5625rem, 1.33rem + 1.16vw, 2.125rem) |
01-tokens.css:11 |
|
--text-3xl |
clamp(1.875rem, 1.44rem + 2.16vw, 2.875rem) |
01-tokens.css:11 |
|
--text-4xl |
clamp(2.25rem, 1.5rem + 3.75vw, 3.75rem) |
01-tokens.css:11 |
|
--leading-tight |
— | 1.12 |
01-tokens.css:11 |
--leading-snug |
— | 1.3 |
01-tokens.css:11 |
--leading-normal |
— | 1.55 |
01-tokens.css:11 |
--leading-loose |
— | 1.72 |
01-tokens.css:11 |
--measure |
68ch |
01-tokens.css:11 |
Motion
Seven durations and ten easing curves, three of which are linear() springs — a real spring feel with no JavaScript. Thirteen of these live in src/16-motion.css rather than the token sheet, which is why they were missing from this reference until the extractor stopped selecting tokens by filename.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--ease-out |
cubic-bezier(.22, 1, .36, 1) |
01-tokens.css:11 |
|
--ease-in-out |
cubic-bezier(.65, 0, .35, 1) |
01-tokens.css:11 |
|
--ease-spring |
linear(0, .18 6%, .53 14%, .89 24%, 1.04 32%, 1.06 40%, 1) |
01-tokens.css:11 |
|
--dur-1 |
110ms |
01-tokens.css:11 |
|
--dur-2 |
190ms |
01-tokens.css:11 |
|
--dur-3 |
300ms |
01-tokens.css:11 |
|
--ease-in |
cubic-bezier(.55, 0, 1, .45) |
16-motion.css:21 |
|
--ease-emphasized |
cubic-bezier(.2, 0, 0, 1) |
16-motion.css:21 |
|
--ease-decelerate |
cubic-bezier(0, 0, .2, 1) |
16-motion.css:21 |
|
--ease-accelerate |
cubic-bezier(.4, 0, 1, 1) |
16-motion.css:21 |
|
--ease-bounce |
linear(0, .004 2%, .016 4%, .062 8%, .137 12%, .5 24%, .68 28%, .824 32%, .93 36%, .995 40%, 1.02 44%, 1.01 52%, 1 60%) |
16-motion.css:21 |
|
--ease-overshoot |
linear(0, .22 8%, .58 16%, .87 24%, 1.06 32%, 1.11 38%, 1.08 46%, 1.02 58%, 1) |
16-motion.css:21 |
|
--ease-snap |
linear(0, .48 12%, .82 22%, .97 32%, 1.02 42%, 1) |
16-motion.css:21 |
|
--dur-0 |
0ms |
16-motion.css:21 |
|
--dur-4 |
420ms |
16-motion.css:21 |
|
--dur-5 |
620ms |
16-motion.css:21 |
|
--dur-slow |
900ms |
16-motion.css:21 |
|
--stagger-step |
45ms |
16-motion.css:21 |
|
--travel |
12px |
16-motion.css:21 |
Controls
The three control heights and the minimum tap target. 44px is the floor a finger needs; every interactive thing in Deck is at least that tall on a touch device even where the visible box looks smaller.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--tap |
44px |
01-tokens.css:11 |
|
--control-h-sm |
34px |
01-tokens.css:11 |
|
--control-h |
42px |
01-tokens.css:11 |
|
--control-h-lg |
50px |
01-tokens.css:11 |
Stacking
Five named layers, a hundred apart. Deck has exactly one z-index utility and five tokens, on the theory that stacking bugs come from competing numbers and the cure is fewer of them, not more.
| Token | Live | Declared value | Declared in |
|---|---|---|---|
--z-sticky |
— | 100 |
01-tokens.css:11 |
--z-nav |
— | 200 |
01-tokens.css:11 |
--z-sheet |
— | 300 |
01-tokens.css:11 |
--z-modal |
— | 400 |
01-tokens.css:11 |
--z-toast |
— | 500 |
01-tokens.css:11 |
Ungrouped
All 144 tokens matched a group. This section is
here so that a token added to src/ that matches none of them
appears rather than disappearing, which is the only way this page can be
trusted to be complete.
Overriding a token
Custom properties inherit, so where you set one decides how far the change reaches.
On :root or the <html> element it is the whole
document; on a component it is that subtree.
Specificity still applies to the declaration that sets the property, so a token set
in your own @layer app beats Deck's :root without
!important — the same rule that makes every other override in Deck
work. What custom properties do not do is participate in the cascade at the
point of use: var(--surface) resolves to whatever
--surface is on that element, whichever layer set it.
A token that resolves to nothing makes its declaration invalid at computed-value
time, which usually shows up as an inherited or initial value rather than as an
error. If a colour comes out black or a size comes out zero, check the token name
for a typo first — var() will not tell you.