The cn utility function combines and merges Tailwind CSS class names, handling conflicts and duplicates intelligently using clsx and tailwind-merge.
Location
lib/utils.ts
Type signature
export function cn(...inputs: ClassValue[]): string
One or more class name values to merge. Can be strings, objects, arrays, or conditional values.
Returns: A single merged string of class names with conflicts resolved.
Implementation
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
The function works in two stages:
clsx - Combines conditional class names and converts objects/arrays to strings
twMerge - Intelligently merges Tailwind classes, resolving conflicts
Why use cn?
Tailwind class conflicts can occur when combining dynamic classes:
// Without cn - both classes applied, but only text-red-500 takes effect
<div className="text-blue-500 text-red-500">Text</div>
// With cn - tailwind-merge removes the conflict
<div className={cn("text-blue-500", "text-red-500")}>Text</div>
// Result: "text-red-500" (last one wins)
Usage examples
Basic class merging
import { cn } from "@/lib/utils"
<div className={cn("px-4 py-2", "bg-primary")} />
// Result: "px-4 py-2 bg-primary"
Conditional classes
<div className={cn(
"px-4 py-2",
isActive && "bg-primary",
isDisabled && "opacity-50"
)} />
// Result: "px-4 py-2 bg-primary" (if active)
// Result: "px-4 py-2 opacity-50" (if disabled)
Class conflict resolution
<div className={cn(
"p-4",
large ? "p-8" : "p-2"
)} />
// Result: "p-8" (if large) or "p-2" (if not large)
// The conflicting "p-4" is removed by tailwind-merge
Object syntax
<div className={cn({
"bg-primary": isPrimary,
"bg-secondary": !isPrimary,
"text-white": true
})} />
// Result: "bg-primary text-white" (if isPrimary)
Array syntax
const baseClasses = ["rounded", "border"]
const variantClasses = isOutgoing ? "bg-primary" : "bg-secondary"
<div className={cn(baseClasses, variantClasses)} />
// Result: "rounded border bg-primary" (if outgoing)
Real-world examples from the codebase
Message bubble styling
components/chat/message-bubble.tsx
<div
className={cn(
"group relative max-w-[75%] px-3 py-2 rounded-lg",
isOutgoing
? "ml-auto bg-primary text-primary-foreground"
: "mr-auto bg-secondary text-secondary-foreground"
)}
>
Avatar status indicator
components/chat/chat-header.tsx
<span
className={cn(
"absolute -right-0.5 -bottom-0.5 h-2.5 w-2.5 rounded-full border-2 border-background",
isOnline ? "bg-accent" : "bg-muted"
)}
/>
Responsive layout
components/chat/chat-app.tsx
<div
className={cn(
"hidden h-full w-full max-w-sm shrink-0 border-border/60 md:flex",
(!isMobile || !activeChatId) && "flex"
)}
>
Benefits
- Conflict resolution - Automatically handles Tailwind class conflicts (e.g.,
p-4 vs p-8)
- Conditional logic - Clean syntax for conditional classes without template literals
- Type safety - TypeScript support via
ClassValue type
- Flexibility - Accepts strings, objects, arrays, and falsy values
- Performance - Optimized for production use
The cn utility is used throughout the codebase in every component for consistent class name handling.
- clsx - Underlying conditional class name library
- tailwind-merge - Tailwind-specific class merging