cn README

The project's README from github.com/shadcn-ui/cn. As one markdown file: README.md.

cn

cn is a new engine for Tailwind class merging and conflict resolution. It replaces tailwind-merge and clsx. Same APIs. Full parity. And it is 30× faster.

import { cn } from "cn"

// conditional joining (like clsx) + conflict resolution (like tailwind-merge)
cn("px-2 py-1", isActive && "bg-blue-500", { "text-white": isActive })

cn has zero dependencies and is framework-agnostic: it works with React, Vue, Svelte, Solid, Astro, or plain server templates, and runs in browsers, Node, Bun, Deno, and edge runtimes. It works in any Tailwind CSS project. You don't need shadcn/ui.

cn is built and maintained by aidenybai and shadcn.

Can I use this today?

Yes. You can replace tailwind-merge and clsx with cn today. It's a drop-in replacement.

Here's a command to migrate:

npx shadcn@latest migrate cn

Install

npm i cn

New project

Import it and go. Nothing to configure:

import { cn } from "cn"

export function Button({
  className,
  active,
  ...props
}: React.ComponentProps<"button"> & { active?: boolean }) {
  return (
    <button
      className={cn(
        "rounded-md px-4 py-2 text-sm",
        active && "bg-primary",
        className
      )}
      {...props}
    />
  )
}

Existing shadcn/ui project

Using the shadcn CLI

npx shadcn@latest migrate cn

Manually

Your components already import cn from @/lib/utils. Replace the wrapper with the one from cn.

// lib/utils.ts
- import { clsx, type ClassValue } from "clsx";
- import { twMerge } from "tailwind-merge";

- export function cn(...inputs: ClassValue[]) {
-   return twMerge(clsx(inputs));
- }
+ export { cn } from "cn";

Then remove clsx and tailwind-merge from your dependencies. If other packages still import them, alias them to cn so your bundle only carries one implementation.

How much faster?

The following benchmarks compare cn against clsx + tailwind-merge, which is what most projects run today.

We ran each library through every workload in its own isolated process with its own warmup, and kept the best of 5 runs.

To see the results for yourself, run pnpm bench. The methodology is in docs/how-it-works.md.

scenario clsx + tailwind-merge cn faster
the call your components make most¹ 320 ns 10 ns 30×
same classes as last render (cache hit) 14 ns 7 ns 1.9×
typical component strings, warm 13 ns 7 ns 1.9×
thousands of recurring strings (a real repo's working set) 2.4 µs 14 ns 172×
cold render, many arbitrary values 3.4 µs 1.1 µs 3.0×
cold render, SSR-style unique strings 2.3 µs 360 ns 6.4×
very first call (page load) 3.2 ms 0.4 ms 7×

¹ cn(base, variant, condition && extra) with stable class strings. This is the shape almost every component call has. cn learns repeated call sequences, so a render loop's calls verify by identity and skip the work entirely.

Real repositories

The rows above are synthetic. pnpm bench:corpus replays every cn() call harvested from 58 open source codebases (144,265 calls) through each library, one isolated process per library and repository, and prints the per-repository table. Geometric mean across the 58 repositories: cn is 37× faster than clsx + tailwind-merge.

cn ships the least JavaScript to parse in every setup, 26 KB minified.

If you want an even smaller bundle with the same performance, see cn build.

Custom themes

cn/config accepts the same { extend, override, prefix } shape, under the same name:

// before
import { extendTailwindMerge } from "tailwind-merge"

const twMerge = extendTailwindMerge({
  extend: { classGroups: { "font-size": [{ text: ["hero"] }] } },
})
// after
import { createCn } from "cn/config"

const cn = createCn({
  extend: { classGroups: { "font-size": [{ text: ["hero"] }] } },
})

Custom validator functions work as-is. fromTheme, validators, mergeConfigs, and defaultConfig are exported from cn/config. Tailwind v4 prefixes are supported: createCn({ prefix: "tw" }).

Coming from tailwind-merge

cn produces the same output as tailwind-merge for every input. We verify this with 356,000 differential tests.

Every export maps to the same name or a familiar one:

tailwind-merge cn
twMerge(...) twMerge(...) from "cn", identical
twJoin(...) twJoin(...) from "cn", identical
extendTailwindMerge(ext) same name, from "cn/config"
createTailwindMerge(fn) createTwMerge(fn) from "cn/config"
getDefaultConfig() defaultConfig() from "cn/config"
fromTheme, validators same names, from "cn/config"
mergeConfigs same name, from "cn/config"
experimentalParseClassName not supported

Gotchas

API

Credits

Thank you to all three authors.

cn (shadcn-ui/cn)

Searching Database…

1.7K ·GitHub
这是什么?cn 是一个新的 Tailwind CSS 类名合并与冲突解决引擎,以相同的 API 和完全一致的输出取代 tailwind-merge 和 clsx,而且快得多。
它是库还是框架?它是一个小巧、零依赖的 JavaScript/TypeScript 库(另带一个可选的 CLI),本身与框架无关,可配合任何框架使用。
怎么把它加进我的项目?新项目直接导入 cn 使用即可。已有的 shadcn/ui 项目可以通过 shadcn CLI 换用,或手动替换 @/lib/utils 里的 cn 封装,并从依赖中移除 clsx 和 tailwind-merge。
必须用 Tailwind v4 吗?是的——cn 面向 Tailwind CSS v4(和 tailwind-merge v3 一样);如果你的项目还在 Tailwind v3 上,应该继续用 tailwind-merge v2。CLI 还要求 Node 20+。
cn 到底能快多少?在合成基准测试中,视场景不同,它比 clsx + tailwind-merge 快约 1.9 倍到 172 倍;在来自 58 个开源仓库的 144,265 次真实调用样本上,几何平均快 37 倍。
换用后会破坏我现有的 tailwind-merge 代码吗?不会,cn 就是按无缝替换设计的:对每个输入都产生与 tailwind-merge 完全一致的输出(经 356,000 项差异测试验证),并且每个导出都对应相同或熟悉的名字。
cn build 和 cn/engine 是干什么的?cn build 是 CLI 背后的库,用于自定义流水线和打包器插件;cn/engine(createEngine)则可以编译构建期表格,得到更小、更专一的产物。
用这个必须有 shadcn/ui 吗?不需要,cn 可用于任何 Tailwind CSS 项目,不要求 shadcn/ui,不过它由 shadcn/ui 作者参与创建并部分维护至今。
Creator
shadcn-ui
Size
1.9 MB
Stars
1.7K
Age
1mo
Commits · 90 Days