The story behind Tailwind's CN function

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
No comments yet. Be the first to comment.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

<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
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.
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 picturecn 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.