LOKE Design System
Lib

CN (Class Names) Utility

Combine class names and intelligently merge Tailwind classes using clsx and tailwind-merge.

CN (Class Names) Utility

cn is a tiny helper that composes class names and then intelligently merges Tailwind classes to avoid conflicts. It wraps clsx for expressive class composition and tailwind-merge for conflict resolution (e.g., p-2 vs p-4, text-red-500 vs text-blue-500).

Use cn for any place you’d write a className string that can be composed from multiple parts (props, conditionals, and local styles).


Why use cn?

  • Compose static, conditional, and array-based classes with a single call
  • Avoid Tailwind conflicts automatically (the “rightmost” value wins after merge)
  • Keep components readable by centralizing class logic

Usage

Basic usage (compose and merge):

import { cn } from '@loke/design-system/cn';

function MyComponent({ isActive, className }: { isActive?: boolean; className?: string }) {
  return (
    <div
      className={cn(
        'rounded border p-2 text-sm',
        isActive && 'bg-primary text-primary-foreground',
        className,
      )}
    >
      Hello
    </div>
  );
}

Merging conflicting Tailwind classes (last one wins):

import { cn } from '@loke/design-system/cn';

const classes = cn('p-2 text-red-500', 'p-4 text-blue-500');
// => "p-4 text-blue-500"

Conditional objects and arrays:

import { cn } from '@loke/design-system/cn';

const size = 'lg' as 'sm' | 'md' | 'lg';

const classes = cn(
  'inline-flex items-center',
  ['rounded', 'font-medium'],
  {
    'px-2 py-1 text-xs': size === 'sm',
    'px-3 py-1.5 text-sm': size === 'md',
    'px-4 py-2 text-base': size === 'lg',
  },
);

Combining props.className with local styles:

import { cn } from '@loke/design-system/cn';

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: 'default' | 'outline' | 'ghost';
};

export function Button({ className, variant = 'default', ...props }: ButtonProps) {
  return (
    <button
      {...props}
      className={cn(
        'inline-flex items-center justify-center rounded-md transition-colors',
        'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
        {
          'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default',
          'border border-input bg-background hover:bg-muted': variant === 'outline',
          'hover:bg-muted': variant === 'ghost',
        },
        className,
      )}
    />
  );
}

API

Prop

Type

Function signature:

// wraps clsx + tailwind-merge
function cn(...inputs: ClassValue[]): string;

How it works

  1. clsx flattens and composes the inputs:
  • Strings of classes
  • Arrays of classes
  • Objects mapping class -> boolean
  1. tailwind-merge takes the composed result and removes/merges conflicting Tailwind classes so you don’t end up with redundant or contradictory utilities.

Tips

  • Keep className props last in cn(...) to allow consumers to override defaults
  • Prefer semantic variants in your component logic, then map them to Tailwind utilities via cn
  • Use arrays/objects when conditions are complex; it keeps the call site readable

Examples

Prevent Tailwind conflicts when combining variants:

const pill = cn(
  'rounded-full text-xs',
  'px-2 py-1',
  'bg-secondary text-secondary-foreground',
  // later variant overrides color safely
  'bg-destructive text-destructive-foreground',
);
// => "rounded-full text-xs px-2 py-1 bg-destructive text-destructive-foreground"

Merging dynamic spacing:

function Card({ compact }: { compact?: boolean }) {
  return (
    <div
      className={cn(
        'rounded-md border bg-card text-card-foreground',
        compact ? 'p-2' : 'p-4',
      )}
    >
      Content
    </div>
  );
}

Notes

  • cn is lightweight and safe to use in hot paths; still, if you’re recomputing large class sets repeatedly, memoize where appropriate.
  • The resolution strategy is Tailwind-specific via tailwind-merge. For non-Tailwind projects, clsx alone is fine, but you’ll lose conflict resolution.