cn
Merge and deduplicate Tailwind CSS class names with automatic conflict resolution.
cn
cn is a utility function that merges multiple class name strings or arrays into a single deduplicated string. It intelligently handles Tailwind CSS conflicts, ensuring that utility classes don't override each other unexpectedly.
cn combines clsx for conditional class support with tailwind-merge for smart Tailwind conflict resolution.
API
function cn(...inputs: (string | undefined | null | Record<string, boolean>)[]): stringReturns a deduplicated class string with Tailwind conflicts resolved.
Features
- Conditional classes: Pass objects with boolean values to conditionally include classes
- Deduplication: Removes duplicate class names
- Tailwind conflict resolution: Automatically resolves conflicting Tailwind utilities (e.g.,
px-2+px-4→px-4) - Null/undefined handling: Safely ignores null, undefined, and falsy values
- Array spreading: Flattens nested arrays of classes
Usage
Basic merging
import { cn } from '@loke/design-system/cn';
// String concatenation with deduplication
const classes = cn('px-2 py-1', 'px-4');
// Result: "py-1 px-4" (px-4 overrides px-2)Conditional classes
import { cn } from '@loke/design-system/cn';
function Button({ variant = 'default', disabled = false }) {
return (
<button
className={cn(
'px-4 py-2 rounded font-medium',
variant === 'outline' && 'border border-blue-500',
disabled && 'opacity-50 pointer-events-none'
)}
>
Click me
</button>
);
}Object notation
const classes = cn('base-class', {
'active-class': isActive,
'disabled-class': isDisabled,
});With component variants (CVA)
import { cn } from '@loke/design-system/cn';
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva('px-4 py-2', {
variants: {
size: {
sm: 'text-sm',
md: 'text-base',
},
},
});
function Button({ size, className }: VariantProps<typeof buttonVariants> & { className?: string }) {
return (
<button className={cn(buttonVariants({ size }), className)}>
Custom Button
</button>
);
}Array spreading
const baseClasses = ['px-4', 'py-2'];
const classes = cn(baseClasses, 'rounded', { 'border border-blue': true });
// Handles nested arrays gracefullyWhy use cn instead of template strings?
// ❌ Without cn: px-2 and px-4 both present (conflicting)
const classes = 'px-2 py-1 ' + (variant === 'outline' ? 'px-4' : '');
// ✅ With cn: px-4 intelligently overrides px-2
const classes = cn('px-2 py-1', variant === 'outline' && 'px-4');Common patterns
Component styling with props
interface CardProps {
padding?: 'sm' | 'md' | 'lg';
hover?: boolean;
className?: string;
}
function Card({ padding = 'md', hover = false, className }: CardProps) {
const paddingMap = {
sm: 'p-2',
md: 'p-4',
lg: 'p-6',
};
return (
<div
className={cn(
'bg-white rounded-lg shadow',
paddingMap[padding],
hover && 'hover:shadow-lg transition-shadow',
className
)}
>
{/* content */}
</div>
);
}Dark mode with variants
const classes = cn(
'text-black bg-white',
'dark:text-white dark:bg-black'
);Responsive utilities
const classes = cn(
'grid gap-4',
'sm:grid-cols-2',
'md:grid-cols-3',
'lg:grid-cols-4'
);Troubleshooting
Classes not applying?
- Ensure the class names are valid Tailwind utilities
- Check that the CSS file is imported (Tailwind must have opportunity to generate the classes)
- Verify there's no conflicting CSS elsewhere
Unexpected overrides?
tailwind-mergeresolves conflicts intelligently but only for known Tailwind utilities- Custom classes without Tailwind prefixes may not be deduplicated
Performance?
cnis optimized and safe to call on every render- The cost is minimal for typical use cases
Import
import { cn } from '@loke/design-system/cn';