Tailwind class merge playground

See exactly what cn() keeps and what it throws away when a caller's className meets a component's own classes, with the losing classes listed.

tw
tw
cn(base, className)tw
rounded-lg py-2 font-medium text-primary-foreground bg-destructive px-6 text-base
3 classes were dropped: a later class won the same group. Order decides, not specificity.
bg-primarypx-4text-sm
rounded-lgpy-2font-mediumtext-primary-foregroundbg-destructivepx-6text-base
Made something you like?Blocks built on this pattern

Questions

What does cn() actually do?+

It joins class strings with clsx, then hands the result to tailwind-merge, which knows which utilities belong to the same group and keeps only the last one in each. That is why a caller can pass bg-destructive and beat the component's own bg-primary without any !important.

Why did my class disappear?+

Because a later class claimed the same group. px-6 beats px-4, text-base beats text-sm, and both of those beat nothing else. The list of dropped classes on this page names every one it removed and the order that decided it.

Why can a class still not override anything?+

Two reasons, usually. The class was placed before the one it should beat, since order is what decides here rather than specificity. Or it is an arbitrary value tailwind-merge cannot group, in which case both survive and the CSS cascade picks the winner instead.

Does cn() cost anything at runtime?+

A little: it parses every class to work out which group it belongs to. Not worth thinking about for a page of components, worth thinking about inside a list that renders thousands of rows, where hoisting the constant part out of the loop is the easy win.

Related tools

The tools are free. So is most of the library.

1935 blocks and 989 pieces for shadcn/ui and Tailwind, built on the same tokens these tools write. Install one with a command and the code is yours.

No signup for the tools. MIT for free blocks, commercial licence for Pro.

Markdown version