Variant explosion in primitive components is a real problem that can quickly
wreck a design system. It usually starts small—maybe you just need a different
color for a Button. But if you keep relying on long, messy stacks of
className strings, your code eventually turns into a "string soup." It gets
even worse when you start mixing boolean props like isPrimary with actual
variant props, making it impossible to tell which rule should actually win.
Class Variance Authority (CVA) is a great way to handle this complexity. It
isn't a component library itself, but rather a "typed recipe" that turns variant
props into a clean string of classes. The real win here is the API honesty; by
using TypeScript's VariantProps, developers don't have to guess which classes
are available. Autocomplete handles the variant or size choices for you.
That said, it's not a silver bullet. Not every design change deserves a new
variant. During code reviews, I try to use one standard: could this variation be
used in another part of the product without needing a rename? If a prop name
includes business logic or specific verbs like approveInvoice or
billingButton, it shouldn't be in your primitive component. That’s the job of
a domain wrapper. Don't let product-specific jargon pollute your core UI system.
When it comes to compoundVariants, only use them for design rules that
actually make sense across different axes. If you find yourself needing a
massive, complicated explanation for a rule, you probably don't need a new
variant—you likely need an entirely different pattern component.
Also, be careful with default variants. A default value is part of your public
contract. Changing a default from primary to secondary can break the look of
your entire app, even if the TypeScript types don't throw an error. Treat
changes to default values as a major version update.
To wrap things up, always include a className prop at the very end as an
"emergency exit" for small, one-off adjustments. Just make sure you're using
something like tailwind-merge so your class order stays predictable and
controlled.
Managing components is all about finding the balance between flexibility and order. If you're too rigid, you'll slow everyone down; if you're too loose, you'll just drown in the chaos. Happy organizing!