Skip to main content

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.

TeamColourSpacingComponent
Feature ABrand green12 / 16 / 20Local button
Feature BMarketing green8 / 16 / 24Legacy button
Feature CProduct green16 / 20 / 32Inline styles
Feature DGrey override12 / 24 / 40Forked card
Sanitised reconstruction

Symptoms, not solutions

  1. Three slightly different greens, two greys, and no canonical neutral.
  2. Spacing chosen by eye. Primary actions wandered between 12 and 20 pixels of padding depending on which feature you opened.
  3. 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.
  4. The reasoning behind a colour or a radius lived in pull-request descriptions, where no designer would ever read it.
  5. 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.

Components only read from the semantic layer. Primitives stay invisible.
/* 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
Recreated panel

One button, every decision

The button is the smallest thing that touches every part of the system. Six token decisions hiding inside one component.

Publish prototype
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.

Default Project title

Neutral surface, no validation copy.

Focus Project title

Ring uses the same focus token as buttons.

Error Project title

Copy carries the error; colour is supporting evidence.

Disabled Project title

Lower emphasis without changing layout.

Specimen panel

What actually changed the work

  1. 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.
  2. Naming is the API. 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.
  3. 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.

If your Figma and component library have drifted apart, I can close that gap

Get in touch →