shadcn/ui 入门(基于 Radix UI)
面向 VOZEB PRO 项目,讲解 shadcn/ui 是什么、如何工作、如何与 Radix UI 配合,以及在本项目中的配置与使用。
0. 什么时候使用 shadcn/ui?
适合使用 shadcn/ui 的场景:
- ✅ 需要一个 AntD 没有或不方便的组件,且想要轻量、好定制的方案 — 比如本项目里就用到了 shadcn 的
Select - ✅ 希望拥有组件源码、能自由修改 — 正是 shadcn 的核心特点(复制进项目,随便改)
- ✅ 配合 Tailwind CSS 4 使用 — shadcn 组件本体就是 Tailwind 工具类写的,和项目的样式统一
- ✅ 需要 Radix 原语的高级交互行为 — 如无障碍(键盘操作、ARIA)完善的下拉、弹窗、气泡等
- ✅ 想要 React Server Components(RSC) 支持 — 本项目的
components.json里开启了rsc: true
不建议使用(或需要权衡)的场景:
- ⚠️ 当 Ant Design(antd)已覆盖需求时 — 本项目的整体 UI 以 antd 为主,antd 主题统一、组件齐全。同一个功能别用两套组件库各实现一遍,会增加维护负担
- ⚠️ 需要跨组件整体风格一致的复杂后台 — antd 的主题体系更完整(表格、表单联动、国际化等),shadcn 偏"一个组件一个组件"颗粒度
- ⚠️ 某些依赖 Radix 原生 API但项目已经用 antd 的等价物时,不要重复引入
在 VOZEB PRO 中的定位: 以 antd 为主体 UI,shadcn/ui(Tailwind + Radix)作为补充,用于项目里已有的少量组件(如
@/components/ui/select.tsx等)。新需求优先看 antd 有没有等价组件,没有再考虑 shadcn。
1. 什么是 shadcn/ui
shadcn/ui 不是传统的组件库,而是一套可复制粘贴到你自己项目里的组件源码集合。
它不通过 npm install 把打包好的组件装进 node_modules,而是把每个组件的完整源代码(TSX + Tailwind 样式 + Radix 依赖)直接写到你的 src/components/ui/ 目录里,让你拥有这些组件的完全所有权,可以随意修改。
核心哲学:复制,而不是依赖(Open source, Copy and paste)。
优势
- 自由定制:组件源码在你项目里,想怎么改就怎么改
- 无版本锁定:不随着上游更新被迫升级
- 样式统一:基于 Tailwind CSS,和你的设计系统一致
- 无障碍:底层由 Radix UI 提供完整的无障碍(Accessibility)支持
与 AntD 的区别
VOZEB PRO 主要用 Ant Design,而 shadcn/ui 是另一套组件体系:
| 维度 | Ant Design | shadcn/ui |
|---|---|---|
| 引入方式 | npm 安装后调用 | 复制源码到项目 |
| 定制性 | 通过主题变量/覆盖 | 直接改源码 |
| 样式方案 | 自带样式 | Tailwind 工具类 |
| 底层 | 蚂蚁自研内核 | Radix UI |
两者可以在同一项目中共存,按场景各取所需。
2. 底层:Radix UI 是什么
Radix UI 是一套无样式(headless)的原语组件库,提供交互行为和无障碍支持,但不带任何视觉样式。
常见的 Radix 原语:
Dropdown— 下拉菜单Dialog— 弹窗Popover— 气泡Toast— 通知Tabs— 标签页Accordion— 手风琴Tooltip— 提示
Radix 的职责
// Radix 只提供行为和结构(无样式)
<Dropdown.Root>
<Dropdown.Trigger>点击</Dropdown.Trigger>
<Dropdown.Portal>
<Dropdown.Content>...
</Dropdown.Portal>
</Dropdown.Root>
shadcn/ui 的职责
shadcn/ui 把 Radix 的"行为"包装上一层 Tailwind 视觉样式,成为可直接使用的完整组件:
// shadcn 组件 = 好看的样式 + Radix 行为
<DropdownMenu>
<DropdownMenuTrigger>点击</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>选项</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
一句话总结:Radix 负责"能点、能弹、键盘能操作",shadcn/ui 负责"看起来好看"。
3. shadcn/ui 如何工作
完整的依赖链是:
shadcn/ui (带样式的组件)
│ 包装样式
Radix UI (无样式原语/行为)
│ 提供交互与无障碍
React DOM (渲染到浏览器)
关键文件
一个 shadcn 组件通常由三部分组成:
- 源码 — 在
src/components/ui/<name>.tsx - 工具函数 — 依赖
cn()(来自src/lib/utils.ts) - 配置文件 —
components.json(定义别名、样式等)
4. 本项目中的配置
VOZEB PRO 的 shadcn/ui 配置在 web/components.json:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {
"@magicui": "https://magicui.design/r/{name}"
}
}
几个关键字段说明:
| 字段 | 含义 |
|---|---|
style |
组件风格(这里是 radix-nova) |
rsc |
是否支持 React Server Components |
tailwind.css |
Tailwind 样式入口(指向 globals.css) |
baseColor |
基础配色(neutral) |
iconLibrary |
图标库(lucide) |
aliases |
路径别名,@/components/ui 是组件目录 |
依赖的工具函数
shadcn 组件依赖 cn() 来合并样式,见 web/src/lib/utils.ts:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
另外,globals.css 里通过 @import "shadcn/tailwind.css" 引入了 shadcn 所需的 Tailwind 基础样式变量。
5. 如何添加一个新组件
使用 shadcn 的 CLI(在本项目 devDependencies 中):
# 在 web/ 目录下执行
npx shadcn@latest add button
npx shadcn@latest add dialog
执行后会:
- 把组件源码写入
src/components/ui/button.tsx - 自动安装所需依赖(如 Radix 原语)
- 更新
globals.css中的必要样式变量
也可以安装多个:
npx shadcn@latest add button dialog dropdown-menu
注:本项目的
components.json还配置了 Magic UI 注册源,可npx shadcn@latest add @magicui/<name>引入 Magic UI 的动效组件。
6. 在项目中使用
以 @/components/ui/select.tsx(本项目已有的组件)为例,使用方式:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export function CitySelector() {
return (
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="选择城市" />
</SelectTrigger>
<SelectContent>
<SelectItem value="bj">北京</SelectItem>
<SelectItem value="sh">上海</SelectItem>
</SelectContent>
</Select>
);
}
配合 cn() 做条件样式
import { cn } from "@/lib/utils";
<div className={cn("p-4", disabled && "opacity-50 pointer-events-none")}>...</div>
7. shadcn/ui + Tailwind 4 的样式变量
shadcn/ui 使用 CSS 变量来定义设计令牌(design tokens),在 globals.css 中通过 Tailwind CSS 4 的 @theme 定义,例如:
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
/* ... */
}
这样在组件里就能用语义化颜色类:
<button class="bg-primary text-primary-foreground">按钮</button>
暗色模式则通过 dark: 变体切换变量:
<div class="bg-background text-foreground dark:bg-background dark:text-foreground"></div>
8. 参考链接
- shadcn/ui 官方文档:https://ui.shadcn.com/docs
- Radix UI 原语:https://www.radix-ui.com/primitives
- Magic UI:https://magicui.design
- Tailwind CSS:https://tailwindcss.com/docs