Context
Three product teams, four component libraries, and a button that existed in eleven visually distinct variants. Nobody had set out to build it that way. Each library was a reasonable local decision that became an unreasonable global one.
The cost showed up in the least visible places first. A designer could not propose a change without knowing which of the four libraries the surface used. An engineer could not fix a focus-state bug once; they fixed it four times, or more often, once and then never again in the other three.
Approach
Start with the audit, not the components
I catalogued every component in production and sorted them by how often they appeared against how much they varied. The result was blunt: eleven components covered seventy-eight percent of all rendered UI. Those eleven became the entire scope of v1. Everything else was explicitly deferred, in writing.
Make the tokens the contract
The system's real API is not its components — it is its tokens. Colour, type, spacing, radius and easing all resolved to a single generated source consumed by both Figma variables and CSS custom properties.
export const ease = {
outQuint: [0.22, 1, 0.36, 1],
outExpo: [0.16, 1, 0.3, 1],
} as const;Because both tools read the same values, "the design and the build disagree" stopped being a category of bug.
Governance, in the smallest form that works
A system with no owner forks. A system with a committee stalls. We landed on one maintainer, a weekly thirty-minute review, and a rule that any team could add a component if they also wrote the tests and the docs. Contribution stayed cheap; divergence got expensive.
Outcome
Four libraries became one. The measured time to assemble a new screen from existing primitives dropped by roughly forty-six percent, and accessibility regressions stopped reaching production because the primitives had keyboard and focus behaviour built in rather than bolted on.
The result I did not expect: design reviews got shorter. When the vocabulary is shared, the conversation moves from "which grey is this" to whether the screen is right.
What I would do differently
I would write the migration codemod before the components, not after. We spent about three weeks on hand-migration that a two-day script would have absorbed, and the hand-migration introduced its own small inconsistencies that took another month to find.