Merge classes with cn()
Components should accept a className prop, but naive concatenation produces conflicts like px-4 px-2. clsx handles conditionals and tailwind-merge resolves conflicts so the last class wins. This project exposes both as cn() in src/lib/utils.ts.
Model variants instead of ad-hoc props
class-variance-authority turns design decisions into a typed API. Each variant is a closed set of options, so invalid combinations fail at compile time.
Style by state with group, peer and data attributes
groupon a parent +group-hover:on children reacts to hovering the whole card.peeron an input +peer-checked:on a sibling builds custom checkboxes without JavaScript.data-[state=open]:pairs perfectly with Radix primitives, which expose their state as attributes.aria-[current=page]:styles the active link from the same attribute screen readers use.
Never build class names dynamically
Tailwind scans source files for complete class strings. A class assembled at runtime never appears in the source, so it is missing from the generated CSS.