Named decisions that keep color, type, and spacing consistent across products — primitives, theme aliases, and accents, browsable like Figma Variables.
A design token is a named decision — a color, size, radius, or type style — stored as a variable instead of a raw value. Tokens turn one-off hex codes and pixel numbers into a shared vocabulary that design and code can both reference, so a change in one place updates every surface that depends on it.
Our system is built on a three-layer architecture: Primitives → Semantic (Theme) → Accent. Each layer has a distinct job, and tokens only ever reference the layer directly below them — never skip a layer, and never hardcode a raw value where a token should be used.
This structure exists to solve a specific problem: raw values tell you what a value is, but not why it was chosen or where it should be used. Layering tokens lets us update a decision in one place and have it propagate everywhere it’s used, while keeping the reasoning behind each decision visible and searchable.
Primitives
Semantic / Theme
Accent
blue-500color-actionaccent-primarygray-100color-surfaceaccent-surfacered-600color-danger(theme override)Theme (semantic) tokens pull from both the primitive palette and the accent ramp. A surface might alias neutral/0, while an action fill aliases accent/500 — so swapping the active accent scheme recolors branded UI without rewriting every component style.
What they are: The raw material of the system — every color, spacing unit, font size, radius, and other base value we’ve decided to allow into the system. Primitives have no meaning attached to them; they’re just a name for a value.
Naming convention: {category}-{scale-step} Examples: blue-500 gray-100 space-4 radius-md
Rules for this layer:
blue-500 doesn’t know if it’s for buttons, links, or icons — that’s the semantic layer’s job.25–975) and alpha steps (a5, a10, a20, a40) of that same base color — see Transparency / opacity below.Why this layer exists: It gives us one place to see the entire palette / scale at a glance, and one place to update a raw value (say, adjusting a color ramp for better contrast) without touching anything that depends on it directly.
What they are: Tokens that carry meaning and intent. This is where a primitive gets assigned a job. Semantic tokens are what most components and designers should actually be reaching for day-to-day.
Naming convention: {property}-{role}[-{state}] Examples: color-action color-danger color-surface-raised color-action-hover
Rules for this layer:
color-danger is correct; color-red is not — if we ever needed to make danger states orange for accessibility reasons, a name like color-red would become actively misleading.Why this layer exists: It’s the translation layer between “here’s a value” and “here’s a decision.” It answers the question a primitive can’t: when should I use this?
What it is: Brand- and variant-level tokens that sit on top of the semantic layer. Where semantic tokens define system-wide roles (danger, surface, action), accent tokens define the specific personality applied across those roles for a given brand, product surface, or variant.
Naming convention: accent-{role} Examples: accent-primary accent-hover accent-surface
Rules for this layer:
Why this layer exists: It isolates brand-specific decisions from system-wide decisions, so a re-brand or new product variant doesn’t require touching semantic logic or components.
Example — a primary button’s background color:
accent-primarycolor-actionsemanticblue-500primitive#3B82F6raw valueIf we rebrand, we change what accent-primary points to. If we redesign our action color system-wide, we change what color-action points to. If we adjust the blue ramp itself, we change blue-500. Each change has a clear, contained blast radius.
Opacity lives in the primitive layer, and shows up in two places:
1. Within each color ramp, as alpha steps alongside the solid scale steps. Each ramp (red, blue, neutral, etc.) carries its standard solid steps (25 through 975) plus a set of alpha variants (a5, a10, a20, a40) that apply a percentage of transparency to that ramp’s base color. For example, in the red ramp:
| Name | Raw value | Opacity |
|---|---|---|
red-500 | #C01717 | 100% |
red-a5 | #C01717 | 5% |
red-a10 | #C01717 | 10% |
red-a20 | #C01717 | 20% |
red-a40 | #C01717 | 40% |
This means transparency is color-aware at the primitive level — red-a20 is specifically “red at 20%,” not a generic overlay value. Every color ramp in the system follows this same pattern, so any ramp can supply its own alpha steps without reaching into a different group.
2. As a dedicated alpha group, for general-purpose, color-agnostic opacity — the “scrim/overlay” use case rather than a color-specific one. This group is split into light and dark subgroups (6 steps each), used for things like overlays and disabled states that need to sit correctly on either a light or dark surface rather than being tied to a specific hue.
Why both exist: the in-ramp alpha steps (red-a20, blue-a40, etc.) are for when a specific color needs to be shown at partial opacity — a tinted hover state, a colored badge background. The alpha group is for neutral, surface-relative transparency — a modal backdrop or disabled-state veil that isn’t tied to any particular hue and needs a light and a dark version to work on either surface.
Percentage-to-hex reference, for translating between the visible percentage and the underlying hex suffix when needed (e.g. exporting to a platform that expects 8-digit hex):
| Opacity | Hex suffix |
|---|---|
| 5% | 0D |
| 10% | 1A |
| 20% | 33 |
| 40% | 66 |
| 60% | 99 |
| 80% | CC |
| 100% | FF |
Semantic tokens compose a base color primitive with an alpha primitive where needed, e.g.:
overlay-scrim = neutral-900 @ alpha-dark-60
badge-danger-subtle = red-a20
Documentation for each alpha/transparent token should show the swatch against both a light and dark background, since transparency only reads correctly relative to what’s behind it — and should list the raw hex value alongside the plain-language opacity percentage, as shown in the Primitive collection view.
In the reference browser below, each row shows:
Use Collections and Groups to navigate the way you would in Figma Variables — start from All, or drill into a hierarchy path when you know the group you need.
Browse collections and groups, inspect name and alias, and copy the CSS variable with the button on each row.
Collections
Groups
theme
Name
bg / solid
--color-bg-solid-1
neutral/25
#FAFAFA
--color-bg-solid-2
neutral/50
#F5F5F5
--color-bg-solid-3
neutral/100
#E5E5E5
--color-bg-solid-base
neutral/0
#FFFFFF
--color-bg-solid-tonal
{accent.25}
--color-bg-solid-accent
{accent.strong}
--color-bg-solid-inverse
neutral/975
#141414
--color-bg-solid-popover
{bg.solid.base}
Before adding a new token, ask:
New tokens should be proposed and reviewed the same way a new component would be — not added ad hoc inside a single feature build.
| Layer | Answers | References | Changes when… |
|---|---|---|---|
| Primitive | What values exist? | Nothing (raw values) | The underlying scale/ramp changes |
| Semantic | When do I use this? | Primitives | System-wide design decisions change |
| Accent | What's this brand's personality? | Semantic tokens | Brand, theme, or product variant changes |