OWolf

BlogToolsProjectsAboutContact
© 2025 owolf.com
HomeAboutNotesContactPrivacy

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:

tsx
import { 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 out false, null, or undefined 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:

tsx
export 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 both bg-blue-500 and bg-red-500, potentially causing conflicts.
  • Tailwind does not automatically resolve these conflicts.

✅ Solution: Using cn to Fix It

tsx
import { 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 replaces bg-blue-500
  • ✅ p-2 replaces p-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

tsx
export 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 to isHighlighted
  • ✅ shadow-lg added due to hasShadow
  • ✅ border-red-500 replaces border

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.