Design systems, from tokens to screens
Primitive, semantic and component tokens, why the middle layer matters, and primitives before screens.
Meer Habib
Senior Mobile Engineer · Chittagong
A design system isn't a Figma file or a component library. It's a set of decisions made once, so nobody has to make them again. Which grey is body text? How round is a card? How fast does a sheet open? Answer each question in one place, and every screen gets the answer for free.
Three layers of tokens
1 · primitive
2 · semantic
3 · component
Primitives are the raw values: gray-950 = #0a0b0d, blue-600 = #0060f0. They describe what something is.
Semantic tokens describe what something is for: ink for text, paper for the page, signal for the one accent, faint for labels. Each points at a primitive.
Component tokens are optional, and only for components with real variation: button.primary uses ink, button.accent uses signal.
The rule that makes the system work: components only ever use semantic tokens. Never a hex value, never a primitive.
Why the middle layer matters
I can tell you exactly what this buys, because it happened on this site.
The first version of this portfolio had a warm palette: beige paper and an orange accent. When I moved to cool greys and a blue accent, the change was one block of CSS. The semantic tokens pointed at new values; not one component changed. Later, writing about contrast, I found my label grey failed accessibility. The fix was one line.
Dark mode works the same way. It isn't a second design; it's the same semantic names pointing at different primitives:
:root {
--paper: #f5f6f8;
--ink: #0a0b0d;
--faint: #686c75;
--signal: #0060f0;
}
.dark {
--paper: #0a0b0d;
--ink: #f2f3f5;
--faint: #80848e;
--signal: #4b8dff;
}In React Native, the same idea is a theme.ts with two objects of the same shape, and a hook that picks one.
More than colour
A complete set of tokens covers every decision people argue about:
- Type scale. A handful of sizes, each with a role: title, body, label, caption.
- Spacing. One unit, usually 4, and a short list of multiples.
- Radius. Chip, control, card, sheet. Rounder as things get bigger.
- Elevation. Two or three shadows, not one per designer.
- Motion. Durations and springs. I use one press behaviour and exactly two springs: one for things that move a little, one for things that travel.
Name by role, never by look
signal, not blue. danger, not red. faint, not gray-400. The day the brand changes to green, a token called blue becomes a lie that lives in every file. A token called signal just gets a new value.
Primitives before screens
Tokens are the vocabulary. Primitives are the first sentences: the small set of components everything else is built from. In Lock Card that's a press wrapper, a selection ring, a labelled shelf, a chip, a segmented control, a slider row and an entrance animation. Once those existed, screens were mostly composition, and every screen felt like part of the same app, because it was built from the same parts.
Keeping it healthy
- Add a token when two things need it. Not before, or you'll have fifty tokens nobody uses.
- Delete what's unused. A system with dead tokens teaches people to ignore it.
- Document with live examples. A page that renders every token and primitive is worth more than any written guideline.
- Change it in one place. If a fix needs edits in five components, something that should have been a token isn't.
The short version
- Three layers: primitive, semantic, component.
- Components use semantics only.
- Dark mode is a remap, not a redesign.
- Name by role.
- Build primitives before screens.
Previous: every screen has five states. Next: component patterns that scale.
Building something like this?
Booking new projects for Q4. Replies within 24h.