mrkeyoor.com_
Mon 28 Sept 06:37 UTC
Dev Toolsevaluationupdated 28 Sept 2026

cn review

cn combines conditional class joining and Tailwind CSS conflict resolution in one zero-dependency package. It is designed as an API-compatible replacement for `clsx` plus `tailwind-merge`, with an optional compiler that trims its conflict tables to the classes an application uses.

Verdict

Our clean run installed 185 packages, built in 6 seconds, and passed the available tests in 17 seconds, making cn easy to trial in a Tailwind 4 application. Use the default import first, then consider cn build only when bundle size or a custom theme gives you a reason. Keep a visual or class-output regression suite during migration because two open issues document cases where its output differs from tailwind-merge.

We ran it

Lab card: what happened when we ran cnScreenshot of cn (github.com/shadcn-ui/cn)
Install✓ · 9s185 packages · 108 MB
Build✓ · 6s
Tests✓ · 17sran, no count parsed
Repo123 files~9,684 lines of source · 10.3 MB · 3 CI workflows

Answers from our run

Does cn build from source?

Dependencies installed in 9 seconds (185 packages), and the build succeeded in 6 seconds. We cloned commit ee3ac80 into a clean Debian container with 3 CPUs and no project-specific setup.

Do cn's tests pass?

The test command failed in our container, and its output did not report a pass or fail count.

Who should not use cn?

Tailwind CSS 3 projects: the README explicitly directs them to tailwind-merge 2 instead.

What are the alternatives to cn?

tailwind-merge, clsx, cnfast. Our clean run installed 185 packages, built in 6 seconds, and passed the available tests in 17 seconds, making cn easy to trial in a Tailwind 4 application.

Setup5/59-second install, build and tests both passed
Docs5/5Migration, internals, plugins, limits, and aliases are explicit
Community4/51,600 stars with a current release and active issue discussion
Maturity4/5Strong checks and CI, though version 0.4.0 has open parity gaps

Who it’s for

Tailwind CSS 4 application teams replacing both clsx and tailwind-merge.
React, Vue, Svelte, Solid, Astro, or server-template developers who want one class helper.
Vite and Next.js applications willing to generate project-specific merge tables at build time.
Performance-sensitive interfaces with many repeated class-merging calls and a regression suite for rendered styles.

Who it’s NOT for

Tailwind CSS 3 projects: the README explicitly directs them to tailwind-merge 2 instead.
Published component libraries using project-fitted tables: the build guide says consumers may use classes absent from the library's source scan.
Codebases that construct utility names such as "p-" + size and will not maintain a safelist: cn build cannot detect those classes.
Users of experimentalParseClassName: the compatibility table lists it as unsupported.
Teams requiring proven byte-for-byte parity before migration: open issues 142 and 23 document reproducible gradient and arbitrary-font differences.
Projects pinned below Node 20 that need the CLI: the documented engine requirement starts at Node 20.

Setup reality

Our sandbox installed 185 pnpm packages in 9 seconds and used 108 MB. The 10.3 MB checkout had 123 files and about 9,684 source lines. The build succeeded in 6 seconds, and the available tests succeeded in 17 seconds.

The default cn import needs no credentials, service, framework plugin, or runtime dependency. Repository work uses Node 20 or newer and pnpm. The optional compiler needs source globs and an output path. Vite and Next.js adapters can regenerate tables during development and production builds.

The monorepo has 3 CI workflow files, no Dockerfile, and no top-level tests directory in our scan. Tailwind 3 users need the older alternative. Build-generated tables also require static class names or a safelist, and published component libraries should use the default table rather than a table fitted to their own sources.

The default import replaces two helpers with one package

Most Tailwind applications use clsx to join conditional values and tailwind-merge to remove conflicting utilities. cn combines those jobs behind familiar calls such as cn, twMerge, twJoin, and clsx. The package has 0 runtime dependencies and exports both ESM and CommonJS builds. It is framework-agnostic, so React is optional even though the migration command comes from the shadcn CLI.

The lowest-risk trial is a small wrapper change. An existing shadcn/ui project can re-export cn from its usual utility file, remove the old packages where nothing else imports them, and keep component call sites unchanged. The README also describes aliases for dependencies that still import clsx or tailwind-merge. Those aliases have limits: a default clsx import will not resolve through the documented mapping, and a bundled copy inside another library cannot be intercepted.

The speed claim depends on compiled rules and repeated calls

The package compiles Tailwind conflict rules into flat lookup tables instead of interpreting a configuration object during each call. Its engine uses typed arrays, integer comparisons, and caches for repeated argument sequences, whole strings, and tokens. The README reports a 30-fold result for its representative component call and a 37-fold geometric mean across 58 open-source repositories. Those are the project's benchmarks, not results from our sandbox.

Its methodology is better documented than a bare chart. Each implementation and workload runs in a separate process with its own warmup, and the harness keeps the best of 5 timed blocks. The internals guide also admits that nanosecond results vary and that cold workloads are synthetic. It says the default entry is about 10.5 KB after minification and gzip, roughly 1.9 KB larger than clsx plus tailwind-merge. Faster calls do not automatically mean a smaller default download.

What happened when we ran it

Our sandbox installed 185 pnpm packages in 9 seconds and occupied 108 MB. The checked-out commit was ee3ac80, containing 123 files, about 9,684 source lines, and 10.3 MB before dependencies. The build completed in 6 seconds. The available test command then completed successfully in 17 seconds.

Nothing failed in those repository checks. The scan found 3 CI workflow files and a pnpm workspace, with no Dockerfile and no top-level tests directory. We did not run the project's performance benchmarks, replay 58 external corpora, or measure a production bundle. Our result establishes that install, build, and test worked in a fresh unprivileged Debian container with 3 CPUs, 8 GB of RAM, Node 22, and no secrets.

Project-fitted tables save bytes but impose source-scanning rules

The normal import ships rules for the full supported Tailwind set. cn build takes another route: it scans an application's files and writes a table containing the conflict groups that appear there. Vite and Next.js adapters watch sources during development and regenerate for production. The guide recommends the default table for most applications and reserves compilation for a smaller bundle or custom theme work.

Static discovery creates the same boundary Tailwind users already know. A class assembled as "p-" + size is invisible to the scanner unless you add it to a safelist. With npm lifecycle hooks, a newly introduced group may pass through without merging until the development server restarts. The framework plugins watch and close that gap. Published component libraries should not ship fitted tables because a consumer can use valid classes the library's own source never contained.

Two open reports weaken the full-parity promise

The README says output matches tailwind-merge, and the repository has a substantial differential and fuzzing setup. Open issue 142 still gives examples where legacy bg-gradient-to-* utilities are assigned to a different conflict group. Open issue 23 reports that non-numeric arbitrary font-[...] values can be treated as font weight rather than font family. Both reports include concrete inputs and observed outputs.

That does not make every migration unsafe. It means you should test the class patterns your design system actually uses, especially gradients, arbitrary values, custom themes, and aliases. The public API table also names one unsupported export, experimentalParseClassName. A migration tool can change imports, but it cannot decide whether a rare semantic difference alters a visible component. Snapshotting merged outputs or key rendered states is cheap insurance.

Tailwind 4 and Node 20 define the supported lane

cn targets Tailwind CSS 4 semantics. The README tells Tailwind 3 projects to stay on tailwind-merge 2. The CLI requires Node 20 or newer, while the runtime works in browsers, Node, Bun, Deno, and edge environments. Custom configuration supports extension, override, prefixes, validators, and theme-derived rules through a separate entry point.

Version 0.4.0 was released on September 22, 2026, the same date as the last push we fetched. GitHub showed 1,600 stars and 5 combined open issues and pull requests. Three were issues and 2 were pull requests in the current listing, including the parity reports above. The MIT license is clear, and 3 CI workflows cover benchmarks, checks, and releases. Recent maintenance is visible despite the young version number.

Output checks matter more than the speed headline

A Tailwind 4 application can try the default import with little ceremony: our install, build, and test sequence took 32 seconds in total. Keep the original helper available on a branch, compare merged output for representative components, and inspect arbitrary classes before removing it. If that passes, the API compatibility and zero runtime dependencies make cn a reasonable replacement.

Stay with tailwind-merge when you need Tailwind 3 support or exact incumbent behavior for the reported edge cases. Use clsx alone when conflict resolution is unnecessary. The optional compiler deserves a later decision, after the plain package proves correct in your codebase. That order captures the simple migration without making source scanning part of day one.

Alternatives

ProjectWhat it isPick it when
tailwind-mergeThe established Tailwind conflict resolver whose configuration and output cn aims to match.pick this instead when you use Tailwind 3, need its complete public API, or cannot accept cn's open parity gaps.
clsxA tiny conditional class-name joiner without Tailwind conflict resolution.pick this instead when you only need conditional joining and want no Tailwind-specific behavior.
cnfastA speed-focused class merge project whose argument-cache idea is credited by cn.pick this instead when you are comparing experimental high-speed implementations and can test their narrower contracts yourself.

What people are saying

  1. [velocity-scout] openqa-cn/jev-browser
  2. [velocity-scout] shadcn-ui/cn

Sources

  1. cn repository and README
  2. How cn works and benchmark methodology
  3. cn build setup and source-scanning limits
  4. Aliasing guide and known limits
  5. Open gradient parity report
  6. Open arbitrary-font parity report
  7. cn 0.4.0 release
  8. Measured commit ee3ac80

More dev tools reviews

kitter · flea · sonicloud_opensdk · GSYVideoPlayer · fyne · agent-manager · the whole board →