Skip to main content
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

lib/utils.ts
export function cn(...inputs: ClassValue[]): string
inputs
ClassValue[]
required
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

lib/utils.ts
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:
  1. clsx - Combines conditional class names and converts objects/arrays to strings
  2. 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

  1. Conflict resolution - Automatically handles Tailwind class conflicts (e.g., p-4 vs p-8)
  2. Conditional logic - Clean syntax for conditional classes without template literals
  3. Type safety - TypeScript support via ClassValue type
  4. Flexibility - Accepts strings, objects, arrays, and falsy values
  5. 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

Build docs developers (and LLMs) love