Here's a number that should give you pause: one Button component with four variants, six sizes, nine radius values, and a boolean flag produces north of four hundred valid combinations. Every single one needs to emit the correct class string. That's not a component — that's a decision tree wearing a prop interface.
The variant prop starts as a convenience. It ends as a contract you can't renegotiate.
The Combinatorial Trap Looks Reasonable at Every Step
The pattern is always the same. You add a Button. It needs primary and danger, so you reach for variant. Then someone needs a ghost button that also signals danger. You add ghost-danger. Then outline-success. Then link-warning. Each new combination feels reasonable in isolation — and six months later you have a prop that accepts seventeen values, most of which are just the cross-product of two orthogonal concerns that were never separated.
This is variant explosion: not a single bad decision, but a series of locally defensible ones that compound into an API surface nobody can hold in their head.
The cost isn't just cognitive. Magic Patterns' analysis of design system drift identifies variant sprawl as a structural workflow problem — one that bakes inconsistency into how teams operate rather than how individual contributors behave. Blaming the engineer who added ghost-danger misses the point. The API invited it.
What You're Actually Encoding When You Collapse Dimensions
The deeper problem with a single variant prop is that it conflates two things that change for different reasons: visual style (how the component looks) and semantic intent (what it means in context).
A ghost button and a danger button are answers to different questions. Ghost is a visual register — low-prominence, minimal ink. Danger is a semantic signal — destructive action ahead. These dimensions are independent. A ghost button can be dangerous. An outline button can be a primary action. Collapsing them into one prop means every new intersection requires a new string value, and the API grows as the product of your visual options times your semantic options rather than their sum.
Separating variant from intent — treating them as distinct props — is the architectural move that stops the explosion. It's not a new idea, but it's one teams consistently skip because the single-prop version looks simpler at v1. It is simpler at v1. It's a trap at v6.
The Button component described in the 7onic design system handles this through CVA's declarative variant matrix — variant, size, and radius as separate axes, each composable independently. The four-hundred-plus combinations become manageable because no single prop is doing more than one job. CVA earns its place in the dependency list precisely because the alternative — nested ternaries or a homegrown lookup map — just re-implements the same idea worse.
The Compound Component Problem Is the Same Trap, Different Shape
Variant explosion isn't limited to style props. It shows up structurally too, in the compound component pattern.
One practitioner's account of building a design system from scratch is instructive here: twenty-two of thirty-eight initial components shipped with compound JSX (Card.Header, Card.Title, Card.Content), twenty-five namespaces total, all of it copied from the shape of Radix and shadcn without copying the constraints those libraries were built under. Eighteen days after v0.1.0, all of it was deleted — not deprecated, deleted — in a single afternoon. The lesson wasn't that compound components are wrong. It was that the pattern had been adopted as a silhouette rather than a solution.
The same logic applies to variant props. Teams copy the shape of a mature library's API without inheriting the reasoning behind it. Radix can afford deep variant systems because it's a behavior library with a stable, well-resourced surface. A startup design system at v0.3 cannot afford the same surface area, because every variant you add is a variant you have to document, test, and maintain when the design direction shifts in Q3.
The Signal That Tells You You've Gone Too Far
The practical heuristic: if adding a new product requirement means adding a new variant string rather than composing existing props, your API has already collapsed dimensions it shouldn't have.
That's the moment to stop and ask whether the new value is genuinely a new visual register, or whether it's the intersection of two things that should have been separate props from the start. Audit the component — not for code coverage, but for whether the prop surface reflects the actual axes of variation in your design.
A Button with four variants and two intent values gives you eight combinations with two props. A Button with eight variant strings gives you eight combinations with one prop — and no room to grow without adding more strings. The math is the same until the product asks for combination nine. Then it isn't.
Keep the dimensions separate. The variant prop is a convenience, not a schema.
