Seen this as a tailwind coder?
<button
className={cn("px-4 py-2 rounded", {
"bg-blue-500 text-white": isPrimary,
"bg-gray-200 text-gray-800": !isPrimary,
})}
>
Click me
</button>
Have you ever wondered why everyone is using the cn
function?
That's what we'll dive into today.
There are two problems cn
solves:
Class conflicts
Conditional classes
Class conflicts
Let's say we have a button component with bg-blue-500
and text-white
classes.
export function Button({ className }) {
return (
<button className={`px-4 py-2 rounded bg-blue-500 text-white ${className}`}>
Click me
</button>
);
}
Now, let's use this button in a component. However, we wanna override the background color to bg-red-500
.
<Button className="bg-red-500">Click me</Button>
The problem with tailwind is that overriding classes is not as simple as adding a new class. The outcome is unpredictable. Whether we put the className
prop of the button at the beginning or the end, we don't know what Tailwind will prioritize.
Hence we use twMerge
from tailwind-merge
to deal with this.
import { twMerge } from "tailwind-merge";
export function Button({ className }) {
return (
<button
className={twMerge(`px-4 py-2 rounded bg-blue-500 text-white`, className)}
>
Click me
</button>
);
}
Huh! That works.
className
will be passed and work as expected.
Conditional classes
We have another problem. What if we want to conditionally apply classes?
We could still use twMerge
for this. But it would become a bit messy.
export function Button({ isPrimary }) {
return (
<button
className={twMerge(
`px-4 py-2 rounded`,
isPrimary ? `bg-blue-500 text-white` : `bg-gray-200 text-gray-800`
)}
>
Click me
</button>
);
}
You can imagine if we had multiple conditional classes, it would become a mess.
That's where clsx
comes in.
It lets us specify the classes as keys and the values as the conditions.
It looks clean and is really convenient.
import clsx from "clsx";
export function Button({ isPrimary }) {
return (
<button
className={clsx(`px-4 py-2 rounded`, {
"bg-blue-500 text-white": isPrimary,
"bg-gray-200 text-gray-800": !isPrimary,
})}
>
Click me
</button>
);
}
cn
into the picture
cn
is a combines both twMerge
and clsx
.
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
And yeah, that's the story of cn
.