Primitive
- sage.500
- space.200
- radius.300
Selected work · Design system build
How a drifting product UI grew into foundations the team would actually use. The case is sanitised here because it was client work; the artefacts and the thinking are the same. A private walk-through is available on request.
Context
Multiple teams shipping in parallel into a UI that had drifted for years. No canonical tokens. A component library that existed on paper but that nobody trusted enough to use. The styles in production looked the way they did for reasons documented in someone's head, or buried in a pull request from two years ago.
Nobody had asked for a redesign. They needed something a new hire could open six months later and follow without a call: named tokens, a component library with documented states, and a one-paragraph 'why' for every non-obvious rule.
The audit grouped drift by colour, spacing, and component choice rather than by screen. That made the system problem visible without exposing the product.
| Team | Colour | Spacing | Component |
|---|---|---|---|
| Feature A | Brand green | 12 / 16 / 20 | Local button |
| Feature B | Marketing green | 8 / 16 / 24 | Legacy button |
| Feature C | Product green | 16 / 20 / 32 | Inline styles |
| Feature D | Grey override | 12 / 24 / 40 | Forked card |
Before
What I built
Four pieces, all reinforcing each other. Removing any one of them brought the old habits back within a sprint.
Primitive → Semantic → Component
Three layers and a rule. Components only consume the semantic middle. Raw values stay where they were defined, and nobody writes a hex code in a component file.
/* Layer 1: primitive (the only place raw values live) */
:root {
--ds-color-sage-500: oklch(0.86 0.13 130);
--ds-space-200: 16px;
--ds-radius-300: 6px;
}
/* Layer 2: semantic (what components reach for) */
:root {
--color-accent: var(--ds-color-sage-500);
--space-control-x: var(--ds-space-200);
--radius-control: var(--ds-radius-300);
}
/* Layer 3: component (semantic in, never primitive) */
.c-button {
padding-inline: var(--space-control-x);
border-radius: var(--radius-control);
background: var(--color-accent);
} This is a recreated variables panel, not a client screenshot. The names are representative of the architecture, with product-specific values removed.
Component anatomy
The button is the smallest thing that touches every part of the system. Six token decisions hiding inside one component.
--color-interactive-bg--color-interactive-fg--space-150 / --space-300--radius-400--font-family-ui / --font-size-100--color-border-focusThe input is recreated from the public design-system rules: state, copy, spacing, and focus behaviour are the proof, not the client UI.
Neutral surface, no validation copy.
Ring uses the same focus token as buttons.
Copy carries the error; colour is supporting evidence.
Lower emphasis without changing layout.
Lessons
A token file nobody uses is a hobby project with extra steps. Most of the actual work was on the boring stuff. Short names. Defaults that matched the common case. Docs that answered the question in the first paragraph instead of the fifth.
The names outlive the values. A team can swap `--color-accent` to a different hex three times and the product stays coherent because everyone reached for the same vocabulary. Rename a token, though, and you will feel it in every diff for a week.
A two-minute drawing of the layers did more for onboarding than the whole usage page. People copy what they can hold in their head, and a paragraph of prose does not stay there.
Private walk-through
The case is sanitised here on purpose. If you want to see the actual Figma library, the tokens running in production, and the docs the team uses every day, I am happy to walk you through it on a call.
Book the walk-through