A design system is usually introduced as a component library. Someone builds forty React components, publishes them to a private registry, and declares the system live. Two years later the team moves to a different framework, or a different rendering model, or simply inherits a codebase that never adopted it — and the whole thing evaporates. All that survives is a Figma file nobody has opened since the migration.
This is not a failure of discipline. It is a failure of what got written down. Components are an implementation of a design system. They are the most visible artefact and the least durable one.
What actually survives
Look at a system that made it through a rewrite and you find the same three layers, in increasing order of longevity:
- Components — the shortest-lived layer. Tied to a framework, a build tool, and a set of conventions that will all change.
- Tokens — the raw values: colour, spacing, type scale, radii, motion timing. These outlive frameworks because they are just data.
- Decisions — the reasoning that produced the tokens and the rules for applying them. This layer is almost never written down, and it is the only one that is genuinely portable.
When a team says "we lost our design system", what they usually lost was layer one. If layers two and three had been recorded properly, rebuilding layer one is a couple of weeks of mechanical work — not a redesign.
A component library tells you what the button looks like. A design system tells you why there are three of them and when to use each.
Tokens as the contract
Tokens are the portable core. The important property is not that they exist but
that the same names appear in design and in code. If your Figma
file calls something surface-2 and the stylesheet calls it
--card-bg-alt, you do not have a system — you have two systems that
happen to agree today.
Practically, that means three tiers:
/* 1. Primitive — the raw value, no meaning attached */
--blue-500: #5B9DFF;
/* 2. Semantic — what the value is FOR */
--accent: var(--blue-500);
--accent-soft: rgba(91, 157, 255, 0.12);
/* 3. Component — only when a component genuinely needs its own knob */
--btn-primary-bg: linear-gradient(135deg, var(--accent), var(--violet));
Most teams jump straight to tier three and end up with four hundred tokens that each have exactly one consumer. That is not a system, it is a stylesheet with extra steps. The test for whether a token earns its place: if you changed it, would you expect more than one thing to move? If not, it is a hard-coded value wearing a costume.
Theming falls out for free
The reason the semantic tier matters is that it makes theming a data change rather than a code change. Redefine the semantic layer under a selector and every component follows, including ones written after the theme existed:
:root { --surface: #101318; }
:root[data-theme="light"] { --surface: #FFFFFF; }
No component needs to know a theme exists. That is the property you want, because it is the one that keeps holding when someone adds a component you did not anticipate.
Write down the decisions
This is the layer teams skip, and it is the one that makes a system survivable. A decision record does not need to be long. Four lines is usually enough:
- What we chose. "Focus rings are 2px solid accent, offset 3px."
- What we rejected. "Not a glow — it disappears against the dark surface."
- Why. "Needs 3:1 contrast against both themes; the glow measured 1.8:1."
- When to revisit. "If we add a third theme."
Six months later, when a new designer asks why focus rings look like that, the answer exists and nobody has to re-litigate it from memory. More importantly, when the codebase is rewritten, whoever rebuilds the components inherits the reasoning instead of guessing from screenshots.
A cheap version of this Keep one markdown file per decision in the same repository as the tokens. Not a wiki — wikis rot in a different building from the code. If the decision and the value live in the same commit history, they change together.
Design states, not screens
The other thing that survives a rewrite is state coverage. A screen is a composition; states are rules. Every interactive component needs six of them drawn before it is finished:
- Default
- Hover (and its absence on touch — hover states must not be the only affordance)
- Focus-visible, which is a legal requirement in more jurisdictions than teams realise
- Active / pressed
- Disabled, with its contrast checked, because "greyed out" is where accessibility usually breaks
- Loading, if the component can ever wait on anything
And for anything that renders data, three more: empty, error, and too-much-data. The last is the one that gets skipped and then discovered in production by the customer with 400 line items.
What to throw away
Systems also fail by accumulation. A few things are worth actively deleting:
- Components with one consumer. Move them back into the feature that uses them. Generalising too early is how a library becomes unmaintainable.
- Variants nobody picked. If the analytics say a size has never been used in eighteen months, remove it.
- Documentation that describes behaviour instead of demonstrating it. A live example beats three paragraphs and never goes stale silently.
The test
Here is a straightforward way to find out whether your system would survive: hand a new developer the token file and the decision records — no component library, no Figma access — and ask them to build a card and a form field. If what comes back is recognisably yours, the system is real. If it is not, the system was the component library all along, and it will not outlive it.
We run that exercise at the end of every design-system engagement, with someone from the client's team rather than ours. It is uncomfortable and it is the most useful hour of the project.