shadcn's New `cn` Package: 30x Faster Drop-in Replacement for clsx + tailwind-merge

shadcn's new `cn` utility merges clsx and tailwind-merge into a single package, achieving up to 30x faster performance via compile-time optimization and multi-layer caching, with a 1.9 KB bundle size increase and a migration CLI for Tailwind CSS v4 projects.

Node.js Tech Stack
Node.js Tech Stack
Node.js Tech Stack
shadcn's New `cn` Package: 30x Faster Drop-in Replacement for clsx + tailwind-merge

Introduction

shadcn has open-sourced a new package named cn that combines the functionality of clsx and tailwind-merge into a single, drop-in replacement. The API remains nearly identical, but the official benchmarks claim up to 30× speedups in certain scenarios.

The Traditional cn() Pattern

For years, projects using shadcn/ui have relied on a utility function typically placed in lib/utils.ts:

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
clsx

concatenates conditional class names, discarding falsy values like false and null. tailwind-merge then resolves Tailwind class conflicts (e.g., px-2 px-4px-4). This two-step approach works well but incurs runtime overhead on every component render.

How cn Achieves Speed

The new cn package moves work from runtime to compile time:

Compile-time rule flattening: tailwind-merge carries ~380 conflict rule groups. cn reads these rules at build time and converts them into a flat lookup table stored in typed arrays, turning conflict resolution into integer comparisons.

Single-pass scanning: At runtime, the input string is scanned once; class names are matched against a character trie in typed arrays.

Early exit: If no classes need removal, the original string is returned immediately.

Three caching layers further accelerate repeated calls:

Argument cache: Remembers the exact arguments object. React components often re-render with identical base styles, variants, and conditional classes, so the same object reference yields an instant cached result. Call order is also memoized, skipping even the cache lookup.

Full-string cache: Caches the complete output string after it appears twice, preventing one-time SSR strings from polluting the cache.

Class-name cache: Stores previously seen individual class names for faster subsequent matching.

Benchmark Results and Real-World Impact

Official benchmarks (run in isolated processes, warmed up, best of 5 runs) show:

Stable arguments (pre-warmed): clsx + tailwind-merge ~13 ns vs cn ~7 ns ( 1.9× faster).

Cold calls with arbitrary values: ~3× faster.

SSR-style one-time strings: ~6.4× faster (first call 3.2 ms → 0.4 ms, 7× ).

Best-case cached scenario: 320 ns → 10 ns ( 30× ).

The project also ran 56,000+ differential test cases, 300,000+ syntax fuzz tests, and 5,000+ custom-config tests (356,000 total). Additionally, 144,265 real cn() calls collected from 58 open-source projects yielded a geometric mean speedup of 37× . The benchmark scripts are publicly available for independent verification.

However, the author cautions that these microsecond-level gains rarely translate to perceptible page-speed improvements. Network latency, heavy computation, DOM updates, and unnecessary re-renders typically dominate render time. The value lies in eliminating a recurring runtime tax for a frequently called utility without changing API or output.

Bundle Size Trade-off and Limitations

Default gzip size: cn ≈ 10.5 KB vs clsx + tailwind-merge ≈ 8.6 KB (+1.9 KB). The increase comes from the pre-compiled lookup tables.

Tree-shaking via cn build : A CLI command can prune the lookup table to only the classes actually used in the project, reducing size for fixed codebases.

Dynamic class names: Constructed strings like "p-" + size cannot be fully analyzed at build time, requiring a safelist in Tailwind config — same limitation as Tailwind's own content scanning.

Migration Path and Recommendations

shadcn provides an automated migration CLI for Tailwind CSS v4 projects: npx shadcn@latest migrate cn The command rewrites imports from clsx, tailwind-merge, and the earlier cnfast, installs cn, and removes the old dependencies if unused. No components.json is required.

Guidance:

New Tailwind v4 projects: adopt cn directly.

Existing projects: run tests, verify custom tailwind-merge configs and edge-case APIs.

Tailwind v3 projects: do not migrate yet ; cn targets v4. Continue with tailwind-merge v2. Node.js 20+ is required for the CLI.

The article concludes that the ubiquitous boilerplate cn() finally has a maintained, optimized successor.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

migrationPerformance OptimizationcachingTailwind CSSclsxtailwind-mergecompile-time optimizationcnfrontend utilitiesshadcn
Node.js Tech Stack
Written by

Node.js Tech Stack

Focused on sharing AI, programming, and overseas expansion

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.