The four pillars
CVA
class-variance-authority gives a type-safe variant map. Variants compose, and defaults apply
automatically.
cn()
cn() is clsx plus tailwind-merge. It handles conditional classes and resolves Tailwind
conflicts, last-wins:
className="px-2" gets both paddings in the class list and
whichever CSS rule happens to win. That is the bug cn() exists to prevent.
Radix and asChild
The asChild prop renders a different element while keeping the component’s styling and
behaviour. It is how a button becomes a link without duplicating the variants.
Slot, which merges its props onto the
child.
Use Radix primitives wherever the component is interactive. Focus management, keyboard
handling and screen-reader semantics are the parts most likely to be got subtly wrong by hand,
and the parts a user notices least until they are broken.
Data attributes
Every component carries data attributes for stable targeting. They are more reliable than class selectors, which change when the variants do.data-portal, pointing at the component’s documentation page — see
component backlinks.
Checklist
- CVA for every visual variant; never an inline conditional class
cn()for everyclassName; never string concatenation- Radix primitives for accessibility where the component is interactive
data-sloton the root element, anddata-variant/data-sizewhere they apply- Named exports only
"use client"only when the component uses hooks, event handlers or browser APIs- Colours from CSS custom properties; no hardcoded hex
- An entry in
registry.json— see contributing