While TailwindCSS is a great styling solution, it has a scalability issue and it may quickly go out of hand when writing huge components with a lot of different states. Even when using clsx utility it's still hard to maintain and extend, and this is when cva (Class Variance Authority) library shines.
If you are using shadcn/ui—which is a fantastic component library, by the way—then you're already using cva, shadcn/ui uses it for managing different component states and configurations, for example, the button component has multiple variants (default, secondary, outline...), if you try to implement it by doing conditions manually, the code will quickly start to look ugly and hard to extend, but with cva, it's quite simple!
let's try to write the same component with those three ways: no-libraries, with clsx, and with cva.
No-libraries:
export const Button = ({ variant = "default", className, ...props }) => {
return (
<button
{...props}
className={`inline-flex items-center justify-center ${
variant === "default" ? " bg-primary text-primary-foreground hover:bg-primary/90" : ""
}${
variant === "secondary"
? "bg-secondary text-secondary-foreground hover:bg-secondary/80"
: ""
// ...
} ${className}`}
// 🟥 Hard to maintain and looks ugly.
/>
);
};
clsx:
export const Button = ({ variant = "default", className, ...props }) => {
return (
<button
{...props}
className={clsx(
"inline-flex items-center justify-center",
variant === "default" && " bg-primary text-primary-foreground hover:bg-primary/90",
variant === "secondary" && "bg-secondary text-secondary-foreground hover:bg-secondary/80",
// ...
className
)}
// 🟧 More readable but still not very scalable.
/>
);
};
And now with cva!:
const buttonVariants = cva("inline-flex items-center justify-center", {
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
// 🟩 Readable and pretty easy to maintain and scale.
});
export const Button = ({ variant, size, className, ...props }) => {
return <button {...props} className={buttonVariants({ variant, size, className })} />;
};
Top comments (0)