cn() — class name composer
A 3-line helper combining clsx (conditional class names) and tailwind-merge (deterministic resolution of conflicting Tailwind utilities). Same primitive shadcn ships.
Why
When you build components with Tailwind, two problems show up constantly:
- You want to conditionally include classes based on state (
disabled,active, etc.) —clsxsolves this. - You want consumers to override your base classes —
tailwind-mergeresolvesp-2 p-4top-4, instead of letting both win randomly.
cn() combines both in one call. Every ShadNG component uses it internally.
Import
typescript
import { cn } from '@shadng/core';Usage
typescript
// Conditional classes
const className = cn(
'inline-flex items-center rounded-md',
disabled && 'opacity-50 pointer-events-none',
pressed && 'bg-accent',
);
// Tailwind conflict resolution
cn('p-2 p-4'); // → 'p-4'
cn('text-sm text-base'); // → 'text-base'
cn('bg-red-500', condition && 'bg-blue-500'); // → 'bg-blue-500' (if condition)
// Inside a component
@Component({
template: `<div [class]="hostClass()">…</div>`,
})
export class Card {
variant = input<'default' | 'ghost'>('default');
hostClass = computed(() => cn(
'rounded-md border',
this.variant() === 'ghost' && 'border-transparent bg-transparent',
this.variant() === 'default' && 'border-border bg-card',
));
}Source
The whole implementation — type signature plus body. Steal it directly into your own code if you don't want the dependency. The peer deps (clsx, tailwind-merge) are small and well-maintained.
cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}When NOT to use
- Static class strings — if your classes never change, a plain string literal is faster and clearer.
- Non-Tailwind class names —
tailwind-mergeonly understands Tailwind utilities. For BEM or other systems, useclsxdirectly. - Inline styles —
cn()is for theclassattribute. For dynamic CSS variables, use[style.--var]bindings.