Systems die from governance, not code
The failure mode is familiar: a strong launch, sixty components, glowing internal demo — and eighteen months later four button variants live outside the library and product teams have quietly forked it. Almost none of that is a technical problem. It is that nobody owned the answer to "I need something the system does not have, what do I do on Thursday?" A system needs a named owner with real hours, a documented contribution path, and a response time commitment — 48 hours to a yes, a no, or a workaround. Miss that window and teams ship their own version, correctly, because they have a deadline. Also publish what the system will not cover. A system claiming to solve everything gets blamed for everything, and "one-off marketing layouts are out of scope" prevents more forks than any lint rule.
Three token layers, not one
Flat token sets stop scaling around the second theme. Use three layers. Primitives are raw values with no meaning attached: blue-600, space-4, a literal hex. Semantic tokens map primitives to intent: colour-action-primary, colour-surface-raised, colour-text-muted. Component tokens map semantics to specific parts: button-primary-background. Product code may only reference semantic and component tokens — the moment a component imports blue-600 directly, dark mode becomes a find-and-replace across the codebase. The payoff is concrete: adding a dark theme means redefining one semantic layer, roughly 40 values, rather than auditing 600 usages. Keep the naming boring and machine-generated where you can, sync it from one source into CSS custom properties and design-tool variables, and never let the two drift into separate hand-maintained lists.
The two-use rule
Abstract on the second real use, not the first, and not the third. On the first use you are guessing which parts vary, and a wrong guess produces a component with eleven props that is harder to use than the markup it replaced. By the third, three teams have shipped incompatible variants and consolidation becomes a migration. Two genuine uses is the point where the axis of variation is visible but the cost of change is still low. "Real" means shipped in production by different people for different purposes — the same card used twice on one page is one use. Corollary: some things should never be abstracted. A layout used once, an illustration, a page-specific animation. Copy-paste is cheaper than the wrong abstraction, and a duplicated block is easier to delete than a shared one is to untangle.
Make adoption the easy path
Adoption is never won by mandate; it is won by the library being the fastest way to do the job. That means installation in one command, a working example you can paste, and props that match how people already think. Measure it honestly — a script that counts library imports versus raw elements per repository gives you a coverage number, and coverage per team tells you where the system is failing. Publish that number. Then close the gap with accessibility: if the library's select is keyboard-navigable, screen-reader labelled and focus-trapped correctly, rebuilding it is a week nobody wants to spend. That is the real moat. Finally, version deliberately, ship codemods with breaking changes, and never make a team choose between upgrading and shipping their roadmap.