Every React component library has a tiny cn() helper. It's the same two lines everywhere. Knowing why it exists saves you from inventing your own broken version.
The two problems cn() solves
- Conditional classes — you want
className="px-4 py-2 disabled:opacity-50"sometimes, plus extra classes only when a prop is set. Naive template literals get ugly fast. - Caller overrides — your
ButtonsetsclassName="px-4"internally, the caller passesclassName="px-6", both end up in the DOM aspx-4 px-6, and Tailwind's cascade picks whichever loaded second. Caller intent is silently lost.
The two libraries
- clsx — handles conditional and array/object class composition. Takes any combination of strings, arrays, and
{ [class]: condition }objects and returns a clean space-separated string. - tailwind-merge — knows Tailwind's grammar. When you give it two conflicting utilities (
px-4andpx-6), it keeps the last one.
Combine them and you get cn() — the canonical helper.
Where you'd write cn()
Once, in src/lib/cn.ts. Every component imports it. cwkPippa's tailwind-merge in package.json is for exactly this — a single cn() used across the chat UI, sidebar, council, admin panels, and everywhere else.
If you're concatenating class strings with
+ or template literals, you're reinventing cn() badly. The two functions are tiny, well-tested, and zero-dependency between them. Install both and move on.