Foundations / Tokens
Every design decision, named once.
A token is the smallest unit of the language: one color, one step of space, one shadow, authored as data and published to CSS and TypeScript from a single source. This is the canonical list of all 213, and the two-tier system that generates them. Click any token to copy its var(...) reference.
Tokens as a Contract
Every token is authored as data, not CSS, in the W3C Design Tokens Community Group format: a JSON object with a reserved $value and a $type like color or dimension. One authored decision publishes to CSS, TypeScript, and native from the same source. The JSON is the contract, and everything downstream honors it. It runs in two tiers.
The raw palette. Eight color ramps of eleven shades each, plus pure white and black. These carry a value and nothing else, no role, no meaning. Product code rarely touches this tier directly. It exists so the semantic tier has something concrete to point at. Naming is descriptive: --color-indigo-600.
The layer you build with. Each token names a role, not a hue, and aliases a primitive: --color-primary, --color-text-body, --color-border. This indirection is what makes the system themeable. Change one alias, and every consumer moves with it. Naming is intentional: role first, variant second.
Style Dictionary reads that JSON, resolves the aliases, and formats each target. The CSS output is a flat block of custom properties on :root, a parallel TypeScript output exposes the same values as typed constants. Both carry a do-not-edit banner: they are build output, not source.
Authored by hand in DTCG format under packages/tokens/src/tokens/. One file per category, each token a $value plus a $type.
Resolves aliases, applies transforms, and formats each platform target. Run once, every output stays in lockstep.
dist/tokens.css as :root custom properties, dist/tokens.ts as typed constants. Do not edit, they are regenerated.
Tier 1: Primitive Palette
The raw material. These ramps are Tailwind CSS's open-source (MIT) default palette, adopted hex-for-hex as the primitive layer; what this system owns sits on top, the mono-indigo constraint and the semantic mapping. Indigo is the brand ramp and the single action accent of the whole system. Slate carries text, surfaces, and borders. The four status ramps, emerald and green for success, amber for warning, red for error, blue for info, feed the feedback tokens. Violet backs the secondary role, a decorative accent only, never for action. Click any shade to copy its variable.
--color-indigo-50 … 950--color-slate-50 … 950--color-emerald-50 … 950--color-green-50 … 950--color-amber-50 … 950--color-red-50 … 950--color-blue-50 … 950--color-violet-50 … 950| Preview | Token | Value |
|---|---|---|
| --color-white | #ffffff | |
| --color-black | #000000 |
Tier 2: Semantic Color
The layer product code consumes. Each token names a role and aliases a primitive. Build with these, never with raw palette shades, so a theme change stays a one-line edit at the alias.
Brand
| Preview | Token | Value |
|---|---|---|
| --color-primary | #4f46e5 indigo 600, the single accent | |
| --color-primary-light | #6366f1 indigo 500 | |
| --color-primary-dark | #4338ca indigo 700 | |
| --color-primary-surface | #eef2ff indigo 50, tinted fill | |
| --color-secondary | #8b5cf6 violet 500 | |
| --color-secondary-light | #a78bfa violet 400 | |
| --color-secondary-dark | #7c3aed violet 600 | |
| --color-secondary-surface | #f5f3ff violet 50, tinted fill |
Text
| Preview | Token | Value |
|---|---|---|
| --color-text-heading | #0f172a slate 900, headings | |
| --color-text-strong | #1e293b slate 800, emphasis | |
| --color-text-secondary | #475569 slate 600, supporting | |
| --color-text-body | #64748b slate 500, body copy | |
| --color-text-muted | #64748b slate 500, muted | |
| --color-text-disabled | #cbd5e1 slate 300, disabled |
Surface and Border
| Preview | Token | Value |
|---|---|---|
| --color-surface | #ffffff cards, panels | |
| --color-surface-secondary | #f8fafc slate 50, sunken fill | |
| --color-surface-hover | #f1f5f9 slate 100, hover fill | |
| --color-background | #f8fafc slate 50, page canvas | |
| --color-border | #e6e8eb default divider | |
| --color-border-light | #e2e8f0 slate 200, soft divider | |
| --color-border-subtle | #f1f5f9 slate 100, hairline | |
| --color-focus | #4f46e5 indigo 600, focus ring |
--color-border and --color-border-light only to separate regions, dividers, table rows, card edges. Why: both sit near 1.2:1 against the surface, below the 3:1 floor WCAG 1.4.11 requires for the boundary of an interactive control. For the visible edge of an input, select, or checkbox, reach for a token that clears 3:1, for example --color-text-muted at #64748b, which is 4.55:1 on white. Never let a subtle border be the only thing marking a control's bounds.Feedback
| Preview | Token | Value |
|---|---|---|
| --color-success | #10b981 emerald 500 | |
| --color-success-strong | #059669 emerald 600, AA fill for a white glyph | |
| --color-success-surface | #f0fdf4 green 50, fill | |
| --color-success-border | #bbf7d0 green 200 | |
| --color-success-text | #166534 green 800, on-surface text | |
| --color-warning | #f59e0b amber 500 | |
| --color-warning-surface | #fffbeb amber 50, fill | |
| --color-warning-border | #fde68a amber 200 | |
| --color-warning-text | #78350f amber 900, on-surface text | |
| --color-error | #ef4444 red 500 | |
| --color-error-strong | #dc2626 red 600, AA fill for white text | |
| --color-error-surface | #fef2f2 red 50, fill | |
| --color-error-border | #fecaca red 200 | |
| --color-error-text | #991b1b red 800, on-surface text | |
| --color-info | #3b82f6 blue 500 | |
| --color-info-surface | #eff6ff blue 50, fill | |
| --color-info-border | #bfdbfe blue 200 | |
| --color-info-text | #1e40af blue 800, on-surface text |
Spacing Scale
A single scale for margin, padding, and gap. One --space-* step in, one step out, so rhythm stays consistent across every component.
| Preview | Token | Value |
|---|---|---|
| --space-0 | 0 | |
| --space-1 | 0.25rem (4px) | |
| --space-2 | 0.5rem (8px) | |
| --space-3 | 0.75rem (12px) | |
| --space-4 | 1rem (16px) | |
| --space-5 | 1.25rem (20px) | |
| --space-6 | 1.5rem (24px) | |
| --space-8 | 2rem (32px) | |
| --space-10 | 2.5rem (40px) | |
| --space-12 | 3rem (48px) | |
| --space-16 | 4rem (64px) | |
| --space-20 | 5rem (80px) | |
| --space-24 | 6rem (96px) | |
| --space-32 | 8rem (128px) |
Border Radius
Eight steps. sm at 6px is the signature control radius. pill is the fully rounded accent, for chips, tags, and toggles, never for wide buttons or cards.
| Preview | Token | Value |
|---|---|---|
| --radius-none | 0 | |
| --radius-xs | 0.25rem (4px) | |
| --radius-sm | 0.375rem (6px) | |
| --radius-md | 0.5rem (8px) | |
| --radius-lg | 0.75rem (12px) | |
| --radius-xl | 1rem (16px) | |
| --radius-2xl | 1.25rem (20px) | |
| --radius-pill | 9999px |
State Layers
Interaction feedback is opacity, not elevation. Each token is the opacity of an indigo overlay painted on a control for hover, focus, press, and drag. Numbers only, so the same layer works on any surface color.
| Preview | Token | Value | Role |
|---|---|---|---|
| --state-hover | 0.08 | Hover on pointer devices, gate with @media (hover: hover) | |
| --state-focus | 0.1 | Focus-visible, the keyboard state | |
| --state-pressed | 0.1 | Active press, primary feedback on touch | |
| --state-dragged | 0.16 | While dragging |
Shadow and Elevation
Three elevation levels plus a focus ring. Each level is two soft, indigo-tinted layers rather than one hard black drop. Shadow means height, never state. See Foundations / Shadows for the full rationale.
| Preview | Token | Value |
|---|---|---|
| --shadow-sm Raised. Buttons and resting cards. | 0 1px 2px 0 rgba(79, 70, 229, .06), 0 1px 3px 0 rgba(15, 23, 42, .04) | |
| --shadow-md Overlay. Dropdowns, popovers, menus. | 0 2px 4px -1px rgba(79, 70, 229, .06), 0 8px 16px -4px rgba(79, 70, 229, .10) | |
| --shadow-lg Floating. Modals, sheets. The ceiling. | 0 4px 8px -2px rgba(79, 70, 229, .06), 0 16px 32px -8px rgba(79, 70, 229, .12) | |
| --shadow-focus Focus ring. Pairs with :focus-visible. | 0 0 0 2px #ffffff, 0 0 0 4px #4f46e5 |
Typography
Three families, eight weights, a ten-step size scale, and the line-height and letter-spacing tokens that pair with them. Rubik carries UI and body, Red Hat Display carries headings.
Families
| Token | Value |
|---|---|
| --font-family-sans | Rubik, system-ui, sans-serif |
| --font-family-display | 'Red Hat Display', system-ui, sans-serif |
| --font-family-alt | Roboto, system-ui, sans-serif |
Weights
| Sample | Token | Value |
|---|---|---|
| Ag | --font-weight-thin | 100 |
| Ag | --font-weight-extralight | 200 |
| Ag | --font-weight-light | 300 |
| Ag | --font-weight-normal | 400 |
| Ag | --font-weight-medium | 500 |
| Ag | --font-weight-semibold | 600 |
| Ag | --font-weight-bold | 700 |
| Ag | --font-weight-extrabold | 800 |
Line Height
| Token | Value | Role |
|---|---|---|
| --leading-none | 1 | Display text |
| --leading-tight | 1.1 | Headlines |
| --leading-snug | 1.25 | Subheadings |
| --leading-normal | 1.5 | Body text |
| --leading-relaxed | 1.625 | Long-form |
Sizes
| Token | Value |
|---|---|
| --font-size-xs | 0.75rem (12px) |
| --font-size-sm | 0.875rem (14px) |
| --font-size-base | 1rem (16px) |
| --font-size-lg | 1.125rem (18px) |
| --font-size-xl | 1.25rem (20px) |
| --font-size-2xl | 1.5rem (24px) |
| --font-size-3xl | 1.875rem (30px) |
| --font-size-4xl | 2.25rem (36px) |
| --font-size-5xl | 3rem (48px) |
| --font-size-6xl | 3.75rem (60px) |
Letter Spacing
| Token | Value | Role |
|---|---|---|
| --tracking-tighter | -0.05em | Display text |
| --tracking-tight | -0.025em | Headlines |
| --tracking-normal | 0em | Body text |
| --tracking-wide | 0.025em | Uppercase labels |
| --tracking-wider | 0.05em | Small caps |
Motion
Three durations and five easing curves. Pair a short duration with an ease-out for enters, an ease-in for exits.
| Token | Value | Role |
|---|---|---|
| --duration-fast | 150ms | Buttons, toggles |
| --duration-normal | 200ms | Cards, panels |
| --duration-slow | 300ms | Modals, reveals |
| Token | Value |
|---|---|
| --easing-linear | cubic-bezier(0, 0, 1, 1) |
| --easing-in | cubic-bezier(0.4, 0, 1, 1) |
| --easing-out | cubic-bezier(0, 0, 0.2, 1) |
| --easing-in-out | cubic-bezier(0.4, 0, 0.2, 1) |
| --easing-spring | cubic-bezier(0.34, 1.56, 0.64, 1) |
Z-Index Scale
A stepped stacking order. Use the token, never a raw integer, so overlays never fight.
| Token | Value | Role |
|---|---|---|
| --z-dropdown | 100 | Dropdown menus |
| --z-sticky | 200 | Sticky headers |
| --z-fixed | 300 | Fixed navbars |
| --z-fab | 400 | Floating action buttons |
| --z-modal-backdrop | 500 | Modal overlays |
| --z-modal | 600 | Modal content |
| --z-popover | 700 | Popovers |
| --z-tooltip | 800 | Tooltips |
| --z-toast | 900 | Toast notifications |
Breakpoints
Minimum widths for the responsive scale. See Foundations / Responsive for usage.
| Token | Value |
|---|---|
| --breakpoint-sm | 576px |
| --breakpoint-md | 769px |
| --breakpoint-lg | 992px |
| --breakpoint-xl | 1200px |
| --breakpoint-2xl | 1366px |
Taxonomy Rules
Three rules keep the system coherent: how to consume a token, how to name a new one, and how a change reaches the screen.
How to consume a token
Reference the variable, never the literal. Reach for the semantic tier first, because it carries the meaning and stays stable across themes. Drop to a primitive only when no semantic token fits, and if you find yourself wanting a raw hex, that is the signal a semantic token is missing, not that you should hardcode.
/* Yes: reference the semantic token */ .card { background: var(--color-surface); color: var(--color-text-body); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); } /* No: a hardcoded value drifts from the system on the next theme change */ .card { background: #ffffff; border-radius: 12px; }
How to name a new token
color, text, secondary becomes --color-text-secondary. Never encode the hue in the name.{color.slate.600}, so the value has one home. If two tokens share a value, both alias the same primitive.$description when the role is not obvious from the name. It becomes the comment in the generated CSS and the note in this reference.How a change propagates
A change starts in the JSON and ends on the screen without a single manual edit downstream. Edit the $value in packages/tokens/src/tokens/, run the Style Dictionary build, and the generated tokens.css and tokens.ts update together. Because every consumer references the variable, the new value reaches every product surface at once. Never edit the generated files: they carry a do-not-edit banner and the next build overwrites them.
dist/tokens.css, dist/tokens.ts, and dist/tokens.json are all build output. If a value looks wrong on screen, fix it in the source token and rebuild, never in the CSS.- Consume
var(--token)references, and prefer the semantic tier. - Author new tokens in the JSON source, then rebuild.
- Name semantic tokens by role, category first, hue never.
- Alias a primitive instead of repeating a hex.
- Add a
$descriptionfor any non-obvious role.
- Hardcode a hex, px, or raw integer that a token already covers.
- Edit
dist/tokens.cssordist/tokens.tsby hand. - Reach for a primitive when a semantic token fits.
- Encode a color name into a semantic token.
- Duplicate a value across tokens instead of aliasing.
Sources
- Design Tokens Community Group, format specification. The open standard, developed under the W3C, that defines a token as a JSON object with a reserved
$valueand$type, and the alias syntax the semantic tier relies on. design-tokens.org and tr.designtokens.org/format - Style Dictionary. The build tool that reads the token source, resolves aliases, applies transforms, and formats the CSS and TypeScript outputs this system ships. styledictionary.com
- Salesforce, primitive and semantic token tiers. The two-tier model, raw primitive values referenced by role-named semantic aliases, popularized for design systems at scale.
- Every token name, value, and description in this page is quoted from this system's source JSON and generated CSS. SPA The elevation ceiling and the pill-as-accent-only rule are house rules specific to this system.