Design system build
From diverged components to one token system.
How a drifting product UI grew into primitive tokens and a Figma library the team actually used.
What I walked into
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.
Four teams, four local truths
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 |
Symptoms, not solutions
- Three slightly different greens, two greys, and no canonical neutral.
- Spacing chosen by eye. Primary actions wandered between 12 and 20 pixels of padding depending on which feature you opened.
- Each feature team had reinvented its own buttons and inputs. Pick two screens at random and they disagreed on what a primary action looked like.
- The reasoning behind a colour or a radius lived in pull-request descriptions, where no designer would ever read it.
- Handover happened by screenshot. Then a slow review cycle of fixing pixels one PR at a time.
Four pieces, all reinforcing each other
Removing any one of them brought the old habits back within a sprint.
Tokens
Three layers with naming treated as the public API. Components only see the semantic middle; raw values stay in the basement where nobody trips over them.
Components
A small set, built against semantic tokens. Every one ships with documented states and accessibility behaviour written where someone might actually find it.
Documentation
Short, opinionated docs that live next to the code. Intent and trade-offs first, usage second. Long documentation pages got ignored, so I stopped writing them.
Design-to-code workflow
Figma variables, code tokens, and components are wired so a change in one place updates everything downstream. Design and engineering finally argue about the right things.
Token architecture
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);
} The same three-layer contract as code
This is a recreated variables panel, not a client screenshot. The names are representative of the architecture, with product-specific values removed.
Primitive
- sage.500
- space.200
- radius.300
Semantic
- accent
- control.x
- control.radius
Component
- button.bg
- button.padding
- button.radius
One button, every decision
The button is the smallest thing that touches every part of the system. Six token decisions hiding inside one component.
- Background
--color-interactive-bg- Foreground
--color-interactive-fg- Padding
--space-150 / --space-300- Radius
--radius-base- Type
--font-family-ui / --font-size-100- Focus ring
--color-border-focus
A vocabulary to survive component states
The 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.
What actually changed the work
- Adoption is the design system. 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.
- Naming is the API. The names outlive the values. A team can swap
--color-accentto 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. - Diagrams beat docs for intent. 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.