A design system is an adoption problem, not a component problem. The components are the easy part. What decides whether a system lives is purpose, structure, technology and governance: whether teams choose it over what they already have, and whether the architecture makes the right thing the default.
Get the token architecture right and good design is baked into the code. Get it wrong and you have built a more complicated way to be inconsistent.
This matters more now that agents write code. An agent inherits whatever the system encodes, and rules that live only in review or in a document do not survive velocity. Standards enforced by the tokens, the components and the pipeline hold at any speed; standards that are remembered do not.
What a system is for
- Standards hold while the team moves at pace. Design, accessibility and engineering rules live in the tokens, the components and the pipeline, so they are enforced rather than remembered.
- A shared vocabulary between design and engineering. A named token means the same thing everywhere; "the dark blue" does not.
- Quality becomes a property of the component rather than of the page that uses it, which is the structural half of our accessibility position.
- One library with theming at the token level, rather than parallel systems per audience.
How it is layered
| Layer | What it holds |
|---|---|
| Tokens | Colour, type, spacing, motion. The base vocabulary |
| Primitives | Buttons, inputs, typography, built on an accessible unstyled foundation |
| Components | Composed from primitives, still content-agnostic |
| Patterns | Whole flows rather than parts. A button is a component; an authentication flow is a pattern |
Above those sit content-bound components matched to the CMS content types. The lower layers stay CMS-independent so the frontend remains portable, and component types are generated from the CMS schema, so a content-model change surfaces as a build error rather than a silent mismatch.
Two kinds of token, and one rule
Primitive tokens hold raw values: grey-50, blue-700, size-16. Semantic tokens name an intention and point at a primitive: background-subtle, text-critical, spacing-md. Components reference only the semantic layer.
A component built on grey-50 has hardcoded an appearance: wrong in dark mode, wrong on a second brand, and nothing in the name gives anyone a reason to change it. One built on background-subtle has expressed an intention, and the token layer decides what that resolves to per theme, mode and product. That one rule is what makes multiple properties a theming exercise rather than a fork, and it is what carries the accessibility rules into everything built on the system, because foreground and background tokens are defined and checked as contrast pairs.
Built as a product, with its own pipeline
The system is built and released as a product in its own right, with a pipeline separate from the applications that consume it. One monorepo, managed with Turborepo, holds the tokens, the component packages, the pattern packages and the documentation site; Turborepo resolves the dependencies between them, so a token change rebuilds and retests everything downstream of it and nothing else, and caching keeps iteration fast as the system grows. Interaction behaviour sits on accessible unstyled primitives, Base UI being the current preference, rather than being hand-rolled. The packages publish as versioned npm packages to a private registry inside the client's own tenancy and access control, and consuming applications take the system by version, never by shared source.
Nothing publishes without passing all four gates:
| Gate | What it does |
|---|---|
| Accessibility | Automated checks against every component, in every state and variant, before publish |
| Standards | Lint and token enforcement, rejecting hardcoded values used in place of semantic tokens |
| Visual regression | Every component and variant diffed on every change, so a token edit cannot quietly alter something three layers up |
| API contract | Prop and interface checks, so a breaking change is caught rather than discovered |
Quality is verified once in the system rather than repeatedly in every application that uses it.
Documentation that cannot drift
Documentation is where most design systems die. It starts comprehensive, drifts out of sync with the code, and stops being trusted. The answer is to generate it from source at build time: a generator extracts prop types, expected values, variants and usage examples from the typed component definitions, so documentation cannot fall behind the code. The same source produces inline IDE documentation, so an engineer sees guidance without leaving the editor, and richer prose is an optional enhancement on top; the baseline exists whether or not anyone writes it. Written once, surfaced in the docs site, the IDE and the playground.
The same principle applies to assets. Contribution should mean dropping a file into a directory and running one command, with the components generated and published automatically.
How it survives
Mandating adoption fails; teams dig in when forced. They adopt a system that is plainly better than what they already have: inline documentation, hooks that catch problems before review, accessible behaviour already handled, small bundles, every standard a team would otherwise enforce by hand on by default. The rest of survival is discipline rather than invention:
- Backwards compatible, so migration runs component by component with no blocking rewrite.
- Versioned like software: semantic releases, changelogs written for both designers and engineers, and a maturity scale so consumers know what is stable, emerging or deprecated. Teams pin a version and upgrade on their own schedule.
- Contribution has to be frictionless, or teams route around the system.
- Usage and adherence reported regularly. Adoption is measured, not assumed.
Ownership once it is stable
Two roles, embedded during delivery rather than trained at handover: a system owner on the design side, holding the token architecture and the visual language, arbitrating contributions and running the reporting rhythm, and a front-end steward on the engineering side, holding the packages, the release process and the pipeline.