The UI Consistency Challenge
As applications grow, keeping buttons, badges, and form elements styled consistently becomes difficult. Writing raw utility classes repeatedly leads to duplicate code and style fragmentation.
Solution: Class Variance Authority (CVA)
class-variance-authority (CVA) allows developers to define component variants declaratively with strict TypeScript types.
Building a Reusable Button Component
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-xl font-medium transition-all focus:outline-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700 shadow-md',
secondary: 'bg-neutral-200 text-neutral-900 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-white',
outline: 'border-2 border-neutral-300 hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-800',
},
size: {
sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button className={twMerge(clsx(buttonVariants({ variant, size }), className))} {...props} />
);
}
Summary
Combining CVA with tailwind-merge provides type safety, eliminates class duplication, and establishes clean design tokens.