A design system is a build artifact
Design systems drift when the design file is treated as the source of truth. Tokens and components belong in the build, with the design file documenting intent.
A design system stops working the moment the design file and the codebase disagree, because from then on every developer has to ask which one is right.
That question has no good answer, and the cost of asking it is why so many design systems are abandoned within two years of a launch announcement. Nobody decides to stop using one. It just becomes faster to guess.
Make the code the source of truth
Tokens — colour, type scale, spacing, easing, breakpoints — live in the codebase and are consumed by every surface. The design file documents intent and rationale. When they diverge, the code wins and the file is updated, not the other way round.
This inverts how most teams start, and it upsets people at first, so it is worth being precise about the claim. The design file is not being demoted as a thinking tool — it is the best place in the world to explore a problem. It is being demoted as a reference, because it cannot be executed, cannot be tested, and cannot be imported by the thing users actually receive.
The practical test: if a developer needs to know the exact value of the disabled state’s border colour, where do they look, and can that place be wrong? If the answer is a design file, the system will drift, because a design file has no mechanism that fails when it is out of date.
Components include their unhappy states
A component that only exists in its ideal state is half a component. Empty, loading, error, too-long-string and reduced-motion are part of the deliverable, and they are where most visual regressions actually appear.
Add to that list, because these are the ones that reach production: the zero state versus the one state versus the many state; a name that is 60 characters; a number that is negative; a currency that puts the symbol after the value; a translation that is 40% longer than the English; the component at 200% browser zoom; the component inside a container narrower than anyone designed for.
None of these are edge cases in the sense of being rare. They are edge cases in the sense of being absent from the mockup. A system that specifies them once, centrally, saves every team that would otherwise improvise a different answer each time.
Enforce it in CI
Visual regression tests on the component library, contrast checks on token pairs, and a lint rule that rejects raw hex values in application code. Without enforcement a design system is a document, and documents drift.
The lint rule is the highest-value item on that list and the least popular. It is also the only one that makes the easy path the correct path: a developer who cannot merge a raw hex value will find the token, and after a few weeks will reach for the token first. Governance that depends on review attention decays the first time a release is urgent.
Contrast checks on token pairs catch the failure mode where each token is fine but a specific combination is not — brand yellow on white, muted text on a tinted surface. Check the pairs your components actually use, not every possible pair, or the check becomes noise and gets disabled.
Visual regression testing deserves a caveat. It is genuinely useful on a component library, where the surface is small and the renders are deterministic. Pointed at full application pages it produces a stream of false positives from fonts, animation timing and content changes, and a test suite people routinely approve without reading is worse than no suite at all — it launders regressions through a green check mark.
The trade-offs
A design system is a product with users, and the users are your own engineers. That means versioning, a changelog, a migration path and someone who answers questions. Teams that ship a component library and then reassign everyone are describing a liability, not an asset.
Too early is a real failure mode. Extracting a system from one product before you have seen the second use case produces abstractions shaped by a single case. The usual heuristic — build it twice, systematise on the third — exists because the third instance is the first honest evidence about what actually varies.
Consistency has a price, and it is sometimes worth paying anyway. A shared system will occasionally be the wrong fit for a specific screen, and the right call is often to use it regardless, because the value is in the predictability rather than in each local optimum. But that needs an explicit escape hatch — a documented way to build something bespoke without forking the whole system — or teams will fork it quietly, which is the same outcome with none of the visibility.
Tokens can be over-abstracted. Three layers of semantic indirection between a colour and a component means nobody can answer “why is this button blue” without opening four files. Two layers — primitive values, then semantic roles — covers almost every real need.
What good looks like a year in
New surfaces get built faster than they did before. Designers stop redrawing the same input field. A colour change ships as one commit rather than a hunt. And nobody asks which file is right, because the question stopped being interesting the day the code became the answer.
This is how we run UI and UX engagements: the design system is delivered as production components, not as a file to be reimplemented — the same reason the web and 3D work can share tokens with a mobile app without the two drifting apart.
Sources and further reading
// TAGS
(02) // LET'S BUILD
START APROJECT
