cva + cn Variant Factory

Pick a variant and size — see the cva() call resolve to a class string, exactly how a shadcn Button works. Then watch tailwind-merge kill conflicting classes.

1 · A cva-powered Button

cva maps a variant + size prop to the right utility classes. One component, many looks, fully type-safe.

variant
size
The call site <Button variant="default" size="default" />
cva resolves to className

2 · Why cn() (tailwind-merge) matters

A consumer passes className to override a default. With plain string join, both px-* survive and the cascade is unpredictable. cn() merges them so the last conflicting utility wins. Edit the override:

Naive: clsx(base, override) — conflicts remain
cn(base, override) — tailwind-merge dedupes
The rule: cn = twMerge(clsx(inputs)). clsx handles conditional classes; tailwind-merge resolves Tailwind conflicts so overrides actually win.