A combinatorial explosion of variants signals the wrong abstraction
Aliases: variant explosion · combinatory explosion · bad variant axes · non-orthogonal props
What it is
When size × tone × icon × loading × shape multiplies into dozens or hundreds of variants, the usual failure is not “we have not drawn every cell.” The axes were chosen wrong. Combinatorial explosion is a diagnostic: some axes are actually a different component, some are slot content, and some are not independent and should never have been free switches.
The signal is about the structure of the matrix, not about abstracting too early (a timing problem) and not about a name so generic that unrelated duties get dumped in (a naming problem). When the grid can no longer be maintained, the cut itself is wrong.
Why it happens
Variant axes are treated as orthogonal: any value on one axis may pair with any value on another, and the implementation branches on the product. In the product many axes are not orthogonal — loading almost always happens on a primary button, not a text link; “circular” only coexists with icon-only buttons. Independent switches on non-independent axes fill the matrix with cells that were never legal, each needing states, tests, and versioning. Maintenance grows with the product; legitimate use grows additively. The gap is the explosion.
The right contraction is not deleting a few unpopular cells. It is redrawing the axes: pack values that always travel together into a handful of modes, give free content to slots, and split a shape that is really another control into another component. Explosion says “this is not one thing with many switches,” not “we still need more switches.”
Where it stops holding
Contrast pairs required for accessibility (a full set under forced high contrast) grow the grid as a compliance cost, not as a bad abstraction. Two implementations forced by platform (desktop menu versus mobile action sheet) should not be crushed onto one variant axis. Arrangements expanded in a design file for a demo, if they never enter code, are canvas noise and must not be used to convict the library. The verdict is the product of the public API in code, not how many frames were tiled in Figma.
Applying it
- List each public axis and its arity, compute the product, then count combinations that actually occurred in production. If they differ by an order of magnitude, stop filling cells and redraw the axes.
- Pack values that almost always travel together into a named mode (icon-round, for example) instead of
shape × hasIcon. - Move free content (icons, extra copy) into slots and off the variant axes.
- How to check: after the redraw, the public product should be small enough to review cell by cell, and previous high-traffic call sites should still express. If the product did not drop and the cells merely hid inside boolean props, the explosion is still there.