shadcn-ui/cn · 上手攻略
- 仓库:shadcn-ui/cn
- 链接:https://github.com/shadcn-ui/cn
- 分类:frontend · utility · tailwind
- 作者:Tom
- 更新:2026-09-04
这是什么
shadcn-ui/cn 是一个 Tailwind CSS 类名合并与冲突解决引擎,由 shadcn 和 aidenybai 联合开发。它的定位非常明确:同时替代 tailwind-merge(twMerge)和 clsx,提供相同的 API、完整的功能兼容,以及 30 倍以上的性能提升。
核心特点: - 零依赖(zero dependencies) - 框架无关(React / Vue / Svelte / Solid / Astro / 纯 HTML 模板均可用) - 运行环境无关(浏览器 / Node / Bun / Deno / Edge) - 无需使用 shadcn/ui 本身,任何 Tailwind CSS 项目均可接入
2026 年 9 月 2 日正式开源,不到一周约 267 Stars(截至查询时),热度极高。
解决什么问题
在 Tailwind CSS 项目中,条件类名的处理一直是痛点:
// 你原本需要两个包:
import { clsx } from "clsx"; // 条件拼接
import { twMerge } from "tailwind-merge"; // 冲突解决
// 然后手写一个 cn() 封装
export function cn(...inputs) {
return twMerge(clsx(inputs));
}
cn 把这个模式直接打包成一个函数,同时完成 clsx 的条件拼接和 tailwind-merge 的冲突解决,并且速度更快。
快速安装
# 方式一:一键迁移(推荐,已有的 shadcn/ui 项目)
npx shadcn@latest migrate cn
# 方式二:全新安装
npm i cn
# 或 Bun
bun add cn
# 或 pnpm
pnpm add cn
迁移已有项目
如果已有 lib/utils.ts 中的 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";
然后删除 clsx 和 tailwind-merge 依赖:
npm uninstall clsx tailwind-merge
⚠️ 注意:如果其他包仍然导入 clsx 或 tailwind-merge,可以用 alias 桥接,参见 aliasing 文档。
核心用法
基本 API
import { cn } from "cn";
// 字符串拼接
cn("px-2 py-1", "bg-blue-500")
// → "px-2 py-1 bg-blue-500"
// 条件类名(clsx 语义)
cn("px-2", isActive && "bg-blue-500", { "text-white": isActive })
// → isActive=true 时:"px-2 bg-blue-500 text-white"
// → isActive=false 时:"px-2"
// 冲突解决(tailwind-merge 语义)
cn("p-4 p-2 p-6")
// → "p-6" (取最后一个同组类名)
// 混用
cn(
"rounded-md px-4 py-2 text-sm",
isActive && "bg-primary",
{ "opacity-50": disabled }
)
进阶配置(与 tailwind-merge 完全兼容)
import { createCn } from "cn/config";
// 自定义扩展(与 extendTailwindMerge 相同用法)
const cn = createCn({
extend: {
classGroups: {
"font-size": [{ text: ["hero"] }]
}
}
});
// Tailwind v4 前缀支持
const cn = createCn({ prefix: "tw" });
cn("tw-text-xl") // → "tw-text-xl"
Tailwind-merge 兼容性对照
| tailwind-merge | cn 中的对应 |
|---|---|
twMerge(...) |
cn(...)(直接调用) |
twJoin(...) |
cn(...)(相同语义) |
extendTailwindMerge(ext) |
createCn(ext) from cn/config |
createTailwindMerge(fn) |
createTwMerge(fn) from cn/config |
getDefaultConfig() |
defaultConfig() from cn/config |
编译时优化(cn/build)
如果对包体积敏感,可以使用 cn build CLI 将合并表编译到项目:
npx cn build --help
⚠️ 动态拼接的类名(如 "p-" + size)无法被编译时检测,与 Tailwind 自身的限制一致,需要用 --safelist 处理。
性能基准
官方 benchmarks(在 58 个开源项目共 144,265 次 cn() 调用上测试):
| 场景 | clsx + tailwind-merge | cn | 提速 |
|---|---|---|---|
| 典型组件调用(稳定类名) | 320 ns | 10 ns | 30× |
| 缓存命中(相同类名重复调用) | 14 ns | 7 ns | 2× |
| 数千种重复字符串(真实工作集) | 2.4 µs | 14 ns | 172× |
| 冷渲染 + 大量任意值 | 3.4 µs | 1.1 µs | 3× |
| SSR 冷启动(唯一字符串) | 2.3 µs | 360 ns | 6.4× |
| 首次调用(页面加载) | 3.2 ms | 0.4 ms | 7× |
核心原理:cn 对重复的调用序列做 argument-identity 缓存(同类调用在第二次起直接跳过计算),在渲染循环中提升效果尤为显著。
典型适用场景
- 已有 shadcn/ui 项目:用
npx shadcn@latest migrate cn一行命令完成迁移,立即获得 30× 提速。 - 新项目直接使用:不再需要同时装
clsx和tailwind-merge,cn一个包搞定。 - 任意 Tailwind CSS 项目:不需要 shadcn/ui 生态,任何框架均可使用。
- 高频渲染场景:在 React/Vue 组件的频繁 re-render 中,
cn的缓存机制效果最明显(官方数据 172× 加速)。
坑与注意
⚠️ 需要 Tailwind CSS v4:cn 依赖 Tailwind v4 的合并语义。如果项目仍在用 Tailwind v3,应继续使用 tailwind-merge v2,不要升级。
⚠️ CLI 需要 Node 20+:npx cn build 等 CLI 工具有 Node 版本要求。
⚠️ 动态类名需 safelist:"p-" + size 这种运行时拼接无法被 cn build 检测,与 Tailwind 自身限制相同,请用 --safelist 配置。
⚠️ 不支持 experimentalParseClassName:如果你依赖 tailwind-merge 的这个实验性 API,cn 暂不支持。
⚠️ 仅合并,不做条件拼接以外的处理:cn 本质是 clsx + tailwind-merge 的融合,不是功能增强。如果你需要 clsx 的数组/对象以外的特殊语法(如 clsx/lite),cn/lite 出口提供纯字符串版本。
⚠️ Bundle 体积:虽然比 clsx + tailwind-merge 双包小,但 26KB minified 仍需评估是否对极简项目可接受。
与同类对比
| 方案 | 依赖数 | 性能 | API 兼容性 | 适用场景 |
|---|---|---|---|---|
| cn | 0 | 30×(组件调用) | 完整兼容 clsx + twMerge | 强烈推荐,所有 Tailwind 项目 |
| tailwind-merge v2 | 0 | 中等 | 独立(无 clsx 功能) | 需要单独合并功能时 |
| clsx + tailwind-merge | 2 | 基准 | 完整 | 暂时不想迁移的存量项目 |
| clsx(单独) | 1 | 最快(仅拼接) | 仅条件拼接,无冲突解决 | 完全不需要类合并时 |
| Tailwind Variants | — | — | 组件变体专用 | 需要定义组件变体体系时 |
一句话推荐结论
如果你的项目同时用
clsx和tailwind-merge(也就是大多数使用cn()封装的项目),cn是毫无犹豫的升级选择:零配置迁移、30 倍提速、零新增依赖,今天就可以跑。