Skip to main content

cn

clsx-style conditional joining followed by a tailwind-merge pass — the familiar cn() helper, exported from tailess.

What it does

Joining first, merging second, and each half decides one thing: the join decides which arguments survive — a falsy one drops out — and the merge decides which classes do.

Usage
import { cn } from "tailess"; cn("px-2 py-1", isActive && "bg-blue-500", "px-4");// → "py-1 bg-blue-500 px-4"   (px-2 dropped in favour of px-4)

That merge pass is why px-2 disappears. tailwind-merge knows both classes set the same property, so the later one replaces the earlier instead of both landing in the string and stylesheet order deciding the winner.

A warm call costs ~123 ns, measured on the built package — see Performance for the rest of the numbers.

cn or ss

ss is a strict superset of cn. Hand it plain strings and the two calls are the same thing, so cn is the one to reach for when there are no breakpoints or states in sight and you would rather say so.

Equivalent
cn(a, isActive && b);ss(a, isActive && b);  // the same call — ss is a strict superset of cn

The habit worth having is cn while a className is only plain strings, and ss the moment a breakpoint or a state shows up — at which point everything, conditions included, moves inside the one call.

Note

cn is one of the two helpers that need no build plugin. Every class it returns is a literal you already wrote, so Tailwind finds it by scanning your source — the API overview lists which helpers do need it.

clsx and tailwind-merge

cn behaves exactly like the twMerge(clsx(...)) helper nearly every Tailwind codebase already has, so tailess drops straight into one.

Only one half of that pairing is a real dependency. tailwind-merge does the merging and is the single package tailess pulls in; the clsxhalf is tailess' own forty-line equivalent. Performance & size has what each half weighs and why that swap was worth making.