Skip to main content
Every component in the registry follows the same four patterns. They are not style preferences; each one removes a specific failure.

The four pillars

CVA

class-variance-authority gives a type-safe variant map. Variants compose, and defaults apply automatically.
TypeScript rejects a variant that does not exist, which is the point — a typo in a string class name is silent, a typo in a variant is a build error.

cn()

cn() is clsx plus tailwind-merge. It handles conditional classes and resolves Tailwind conflicts, last-wins:
Without the merge, a caller passing 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.
Internally the component swaps its element for a Radix 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.
Components also carry 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 every className; never string concatenation
  • Radix primitives for accessibility where the component is interactive
  • data-slot on the root element, and data-variant / data-size where 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