2025-03-14 Web Development
Styling Custom Components With The cn Utility
By O. Wolfson
When building React components with Tailwind CSS, you often need to:
- Merge multiple class names dynamically.
- Allow users to override default styles.
- Apply styles conditionally based on props.
Manually handling these cases can be messy. That’s where cn
comes in.
🚀 What is cn
?
cn
is a helper function that:
- Merges multiple class names into a single string.
- Removes redundant or conflicting Tailwind classes so that only the intended styles apply.
- Allows incoming
className
props to override defaults, giving users full control over styles. - Supports conditional classes, making styling more flexible.
Though cn
is commonly used in shadcn/ui, it's a general-purpose utility that you can use in any React + Tailwind project.
🔹 How cn
Works Internally
A typical cn
function is implemented like this:
tsximport { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
How It Works:
clsx(inputs)
: Combines and filters outfalse
,null
, orundefined
values.twMerge(...)
: Ensures conflicting Tailwind classes are resolved, with later ones taking priority.
Now, let’s see why this is useful.
🎯 Problem: Styling a Custom Component Without cn
Imagine a Button
component:
tsxexport function Button({ className }: { className?: string }) {
return (
<button className={`px-4 py-2 bg-blue-500 text-white ${className}`}>
Click Me
</button>
);
}
🔴 Issue:
- If someone uses
<Button className="bg-red-500" />
, the button will have bothbg-blue-500
andbg-red-500
, potentially causing conflicts. - Tailwind does not automatically resolve these conflicts.
✅ Solution: Using cn
to Fix It
tsximport { cn } from "@/lib/utils"; // Assuming you've placed `cn` in `lib/utils.ts`
export function Button({ className }: { className?: string }) {
return (
<button className={cn("px-4 py-2 bg-blue-500 text-white", className)}>
Click Me
</button>
);
}
Now, if someone does:
tsx<Button className="bg-red-500 p-2" />
The final result will be:
html<button class="bg-red-500 text-white p-2">Click Me</button>
- ✅
bg-red-500
replacesbg-blue-500
- ✅
p-2
replacesp-4
- ✅ The component remains fully customizable and conflict-free
🔹 Extending Styling with Component Props
We can take this further by using props to conditionally apply styles.
🎯 Example: A Card
Component with Conditional Styling
tsxexport function Card({
className,
isHighlighted,
hasShadow,
}: {
className?: string;
isHighlighted?: boolean;
hasShadow?: boolean;
}) {
return (
<div
className={cn(
"p-4 border rounded",
isHighlighted && "bg-yellow-200",
hasShadow && "shadow-lg",
className
)}
>
Card Content
</div>
);
}
Usage:
tsx<Card isHighlighted hasShadow className="border-red-500" />
Final Output:
html<div class="p-4 border rounded bg-yellow-200 shadow-lg border-red-500">
Card Content
</div>
- ✅
bg-yellow-200
added due toisHighlighted
- ✅
shadow-lg
added due tohasShadow
- ✅
border-red-500
replacesborder
This approach makes components more reusable and customizable without losing control over default styles.
🎯 When to Use cn
Use cn
whenever:
✔ You need to merge Tailwind classes dynamically.
✔ You want to allow className
overrides safely.
✔ You need to conditionally apply styles based on props.
✔ You want to prevent class conflicts in reusable components.
🔹 Final Thoughts
cn
isn't just for shadcn/ui—it’s useful for any React + Tailwind project.- It helps you write cleaner, conflict-free components with customizable styles.
- Combining it with props for dynamic styles makes components even more powerful.
By mastering cn
, you'll improve your workflow and make your React components more flexible and maintainable.