
Most teams blame the component library when their design system collapses. The real problem is almost always somewhere else entirely.
There's a moment in the lifecycle of most product teams when someone proposes building a design system. The pitch is compelling: consistency across screens, faster development, a shared language between designers and engineers. So the team spins up a component library, builds out a Button, an Input, a Modal, and a handful of layout primitives. Six months in, the system has fifty components and a Storybook that nobody opens. A year in, engineers are copy-pasting components and overriding styles with !important. Two years in, someone proposes starting over.
I've watched this cycle play out more than once, and the diagnosis is almost always the same: the team invested heavily in components and almost nothing in the system around them. Components are the visible output. The system is the decision-making infrastructure — the tokens, the constraints, the governance, the documentation — that determines whether those components stay coherent as the product grows.
A component library is a product. And like any product, it fails not when the code is bad, but when nobody agrees on who it's for and what problem it solves.
Tokens are the real foundation
Design tokens — the named values for colors, spacing, typography, shadows, and radii — are where a design system either holds together or starts to unravel. Not because the tokens themselves are complex, but because they force conversations that teams would rather skip.
What's the difference between color.primary and color.brand? When does spacing.md apply versus spacing.lg? What happens to the token system when you add a dark mode, or a white-label variant for a different client? These are design questions with engineering consequences, and they need to be answered before you write a single component, not after you have eighty of them.
The teams that get this right usually define tokens at two levels: primitive tokens (the raw values — blue-500: #3b82f6) and semantic tokens (the usage-driven aliases — color.interactive.default: blue-500). The semantic layer is what makes theming possible without touching every component. Skip it, and every theme change becomes a grep-and-replace across your entire codebase.
- Primitive tokens define the palette:
gray-900,blue-500,space-4 - Semantic tokens assign meaning:
color.text.primary,color.bg.surface - Components consume semantic tokens only — never primitives directly
- Themes swap semantic values without touching component code
The variant explosion problem
One of the most common signs of a design system under stress is Button. Not the Button component specifically, but what it looks like after eighteen months: variant="primary", variant="secondary", variant="ghost", variant="link", variant="destructive", variant="outline", variant="subtle". Seven variants, none of them clearly differentiated in the product, half of them added because a designer needed something slightly different and the fastest path was adding a new variant instead of having a conversation about when to use which.
This is a governance failure, not a technical one. A useful pattern here is to define variant rules in writing — not in code, but in documentation that lives next to the component. When is ghost appropriate? What's the hierarchy between primary and secondary? If you can't write that documentation clearly, the variant probably shouldn't exist.
Documentation is part of the component
The teams that maintain healthy design systems over time treat documentation as a first-class deliverable — not as a nice-to-have that gets written after the component is built. Every component should have three things documented before it ships: when to use it, when not to use it, and what accessibility behavior is expected.
That last point gets skipped more than it should. A design system that doesn't encode accessibility at the component level pushes that responsibility onto every engineer who uses the system — which means it gets done inconsistently, or not at all. aria-label, keyboard navigation, focus management: these aren't edge cases. They're part of what the component is.
The hard truth about adoption
A design system that engineers don't use is just a side project with good intentions. Adoption is a product problem, not a technical one, and it deserves the same attention you'd give to any other product challenge. What are the friction points? Where do engineers reach for custom solutions instead of the system? What's missing?
The most effective thing I've seen for improving adoption is reducing the time between "I need a component" and "I have a component." That means good search in Storybook, meaningful prop names, copy-pasteable code examples, and a clear process for requesting new components or variants. The system has to be easier to use than rolling your own. If it isn't, people won't use it, and no amount of internal advocacy will change that.
Conclusion
The best design systems I've worked with share something that has nothing to do with code quality: they're treated as living infrastructure, not finished products. They evolve with the product, they have clear owners, and they're measured by whether they actually speed up the work — not by how many components they have.
If you're starting one, resist the urge to build the full component catalog first. Define your tokens, write your principles, agree on governance, and build components as the product needs them. You'll end up with fewer components and a much more useful system.