Design systems are easy to start and hard to keep. The first version is a pleasure to build — a clean set of components, a palette everyone agrees on, a documentation site nobody has had time to contradict yet. Eighteen months and four teams later there are nine buttons, three of which are called Button, and a colour variable named blue-new-final. Nothing went wrong in a single dramatic moment. It eroded.
Scale is the whole problem. A system for one product and three designers needs very little structure. A system for a complex web application — dense interfaces, many teams, long-lived code, real accessibility obligations — needs an architecture, and the architecture has to be decided early, because retrofitting it is the expensive version.
The short version
- A design system is a product. Its users are teams, and adoption is its only real metric.
- Three token tiers — primitive, semantic, component — or theming becomes a rewrite.
- Prefer composition over configuration. Every new boolean prop doubles the states you have to test.
- Accessibility belongs in the component, not in the guidance. Teams inherit it or they skip it.
- Versioning and deprecation decide whether the system can ever change again.
- Undocumented components are unfindable components, and unfindable components get rebuilt.
How systems break at scale
Component sprawl. A team needs something slightly different, cannot get it into the shared library this sprint, and ships a local copy. Reasonable each time, fatal in aggregate. Within a year nobody can answer which component is canonical.
Token drift. Hard-coded hex values appear in product code because the right token did not exist or could not be found. These are invisible until the first rebrand, when they surface all at once.
Variant explosion. Components accumulate props until the combinations are untestable and the API is unreadable. Nobody removes anything, because something somewhere might use it.
Documentation rot. Docs written once at launch, drifting from the code with every release, until teams stop trusting them and start reading the source instead — at which point the documentation has negative value.
No owner. The system is everyone’s responsibility, which means contributions stall, questions go unanswered and teams route around it. This is the failure underneath most of the others.
Nothing goes wrong in a single dramatic moment. The system erodes, one reasonable exception at a time.
The architecture: four layers
1. Tokens — the decisions
Colour, type scale, spacing, radii, elevation, motion durations, breakpoints. Nothing visual, no components. This layer is the only place a raw value is ever written, and everything above it refers downward by name.
Rule: if a hex code appears anywhere above this layer, the system has a hole in it.2. Primitives — the unbreakable units
Button, input, select, checkbox, label, icon, text, stack, grid. Small, heavily used, no business logic. Accessibility is solved here once — focus handling, labelling, roles, keyboard behaviour — so every team inherits it without thinking about it.
Rule: a primitive knows nothing about where it is used.3. Patterns — the combinations you keep rebuilding
Form field with label, help text and error. Data table with sorting, selection and empty state. Modal with focus trap. Page header with breadcrumb and actions. This is the layer complex web apps actually live in, and the layer most systems neglect while polishing buttons.
Rule: if three teams have built it separately, it belongs here.4. Templates — the shapes of whole screens
List-and-detail, dashboard, wizard, settings page, search-and-filter. Not shipped as locked components but as documented arrangements with working examples. They answer “what should this screen look like” before anyone starts arranging patterns by instinct.
Rule: templates are guidance with code attached, not a cage.Tokens are the part that decides everything else
Three tiers, and the middle one is the one teams skip and later regret.
Primitive tokens are raw values with descriptive names: blue.600 = #4F46E5, space.4 = 16px. They describe what something is. No component ever references them directly.
Semantic tokens describe intent: action.bg = blue.600, surface.raised = neutral.0, text.danger = red.700. This is where theming lives. Dark mode, a second brand, a high-contrast mode — all of them are remappings at this tier, and none of them touch a component.
Component tokens are the last mile: button.primary.bg = action.bg, input.border = border.default. They let one component change without affecting its siblings, which is what you need the first time a designer wants a slightly different button without disturbing twenty other things.
Change the controls below and watch the mechanism: the component layer never changes, yet the interface does.
Token workbench
Switch brand, theme and density. Watch which tier actually changes.
Brand
Theme
Density
Primitive tokens
Raw values. Named for what they are.
Semantic tokens
Named for intent. This is the tier that theming rewires.
Component tokens
Never change here. They point at intent, not at values.
Designing component APIs that survive
The single most common scaling failure is a component that grew one prop at a time until nobody could reason about it. The arithmetic is unforgiving.
| Properties | Combinations | Consequence |
|---|---|---|
| 3 variants × 3 sizes | 9 | Reviewable by eye. Fine. |
| + 5 states (hover, focus, active, disabled, loading) | 45 | Still testable, needs a visual snapshot suite. |
| + 2 themes | 90 | Nobody checks them all; some combinations are certainly broken. |
| + icon-only, full-width, destructive booleans | 720 | The component is now unreviewable, and most combinations are nonsense. |
Four rules keep this in hand.
Composition over configuration. If a component needs an icon prop, an iconPosition prop and an iconOnly prop, it should have accepted children instead. Let consumers compose; do not try to anticipate every arrangement with a flag.
One axis per prop. variant handles emphasis. size handles scale. Do not create primaryLarge. Orthogonal props stay comprehensible as they multiply; merged ones do not.
Make the right thing the default. The component used with no props at all should be the correct choice for the common case — accessible, correctly sized, sensibly spaced. Teams follow defaults far more reliably than they follow documentation.
Prefer an escape hatch to a new prop. One documented way to extend a component for a genuinely unusual case prevents a dozen one-off props added for cases that were never going to recur.
Accessibility goes inside the component — focus management, ARIA roles, keyboard handling, contrast-safe token pairings. Guidance that asks teams to add it themselves is guidance that gets skipped under deadline. Build it in once and it ships everywhere by default.
Governance: the part nobody wants to write
A system without governance stops changing, and a system that stops changing gets abandoned. Four mechanisms are enough.
A named owner, with real time allocated. Not a committee. Someone who answers questions within a day, reviews contributions and makes the final call. Design systems run as a side project decay at a predictable rate.
A contribution path product teams can actually use. Documented, with a realistic turnaround. If getting a change into the system takes longer than copying the component locally, every rational engineer will copy it locally. Your contribution process competes with Ctrl+C, and it is currently losing.
Semantic versioning with a deprecation policy. Breaking changes announced, with a migration path, a codemod where possible, and a stated support window. Without this you can never remove anything, and a system that can only grow eventually collapses under its own surface area.
A regular review of what is actually being used. Scan the codebase quarterly. Components nobody uses get removed. Local components that keep reappearing in product code are the system telling you what it is missing — that list is your roadmap.
Documentation that gets read
Teams do not read documentation; they search it, under pressure, having already half-decided what to do. Design for that. Live rendered examples that can be copied in one action. The reason a component exists and, critically, when not to use it. Accessibility behaviour stated explicitly. The props table generated from the source so it cannot drift. Documentation that lives alongside the code and ships with it, rather than in a separate site updated by goodwill.
The highest-value page in most systems is not a component page at all. It is the one that says which component to use for a given job — the decision page. Without it, people pick by name, and names are a poor guide.
Adoption is the only metric that matters
A system nobody uses is a well-maintained liability. Four numbers tell you the truth.
- Coverage. What share of interface code imports from the system rather than defining its own.
- Hard-coded values. Count hex codes and raw pixel values in product code. This number should fall every quarter; if it rises, tokens are missing or unfindable.
- Local duplicates. How many components exist in product code that the system already provides. Each one is a specific failure with a specific cause worth asking about.
- Time to first screen. How long a new engineer takes to build a standard screen using only the system. This is the number that persuades leadership.
Your contribution process competes with copy and paste. Make sure it can win.
Where systems go wrong
| Failure | Looks like | Prevention |
|---|---|---|
| Built before it was needed | 120 components, 4 in use | Extract from shipped product code; never build speculatively. |
| No semantic token tier | Dark mode becomes a rewrite | Three tiers from day one, even with one theme. |
| Design and code diverge | The library and the app no longer match | Shared tokens generated from one source for both. |
| Contribution is too slow | Local copies everywhere | A published turnaround the owner is accountable for. |
| Nothing is ever deprecated | Nine buttons | Versioning, migration guides, enforced sunset dates. |
| Docs separated from code | Examples that no longer run | Docs in the repo, props generated from source. |
| Patterns layer skipped | Every team builds its own data table | Treat repeated compositions as first-class deliverables. |
Where to start
Do not begin by building components. Begin by auditing what your product already contains — every button, every input, every table, screenshotted and grouped. That audit does three things at once: it shows the real inconsistency rather than the suspected kind, it tells you what to build first by frequency, and it makes the business case without a single slide of theory.
Then set up the token tiers properly, extract the five most-used components from code that already ships, document them where engineers already look, and name an owner. That is a system. Everything after it is accumulation, which is the easy part — provided the architecture underneath can take the weight.
How we build design systems at Adi Design. Audit first, three-tier tokens from day one, accessibility built into primitives, and documentation that ships with the code. We hand over a system with a named owner and a contribution process, not a component library. If you are planning one, get in touch.