shadcn/ui 组件开发
一、用途 shadcn/ui 是一套 React UI 组件开发模式,以 Radix 基础组件(Radix primitives)配合 Tailwind 样式实现。适用于构建 UI 组件、使用 CVA(class-variance-authority)变体、实现复合组件(compound components)等场景。
二、CLI 命令 初始化:
npx shadcn@latest init
该命令创建 components.json 配置文件,并设置 Tailwind CSS 配置、主题用的 CSS 变量、cn() 工具函数以及所需依赖。
添加组件:
添加单个组件
npx shadcn@latest add button
添加多个组件
npx shadcn@latest add button card dialog
添加全部可用组件
npx shadcn@latest add --all
注意包名在 2024 年发生变化:旧版(已弃用)为 npx shadcn-ui@latest add,当前为 npx shadcn@latest add。
常用选项:-y, --yes 跳过确认提示;-o, --overwrite 覆盖已有文件;-c, --cwd <cwd> 设置工作目录;--src-dir 使用 src 目录结构。
三、执行条件(Gates) 在执行 init / add 前按顺序检查(仅在未运行 CLI、例如直接复制片段时可跳过):
- 工作目录:pwd 与 package.json 能确认接收 components/ 与 components.json 的应用根目录(若 shell 在别处,使用 -c <cwd>)。
- init 之后:components.json 存在于该应用根目录(或 monorepo 布局中对应的文档化路径)。
- 使用 --overwrite 前:能够列出将被替换的已跟踪文件,或版本控制显示该变更是有意且可恢复的。
四、快速参考 cn() 工具函数:
import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }
基础 CVA 模式:
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva( "base-classes-applied-to-all-variants", { variants: { variant: { default: "bg-primary text-primary-foreground", outline: "border bg-background", }, size: { sm: "h-8 px-3", lg: "h-10 px-6", }, }, defaultVariants: { variant: "default", size: "sm", }, } )
function Button({ variant, size, className, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>) { return ( <button className={cn(buttonVariants({ variant, size }), className)} {...props} /> ) }
export { Button, buttonVariants }
五、组件结构(Component Anatomy) Props 类型模式:HTML 元素使用 React.ComponentProps<"div">;Radix 基础组件使用 React.ComponentProps<typeof RadixPrimitive.Root>;带 CVA 变体时同时合并 VariantProps。
asChild 模式:通过 @radix-ui/react-slot 实现多态渲染:
import { Slot } from "@radix-ui/react-slot"
function Button({ asChild = false, className, variant, size, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size }), className)} {...props} /> ) }
用法示例:<Button>Click me</Button> 渲染 button;<Button asChild><a href="/home">Home</a></Button> 渲染带按钮样式的 a;也可与 Next.js Link 配合。
data-slot 属性:每个组件都带有 data-slot 以便 CSS 定位,例如 data-slot="card"、data-slot="card-header",支持选择器嵌套定位与 has() 条件布局。
六、组件模式与样式技术 复合组件:导出 Card、CardHeader、CardTitle、CardDescription、CardContent、CardFooter 等,各部分分别定义 data-slot 与基础类。
CVA 变体:支持多个维度(variant、size)、复合变体 compoundVariants、以及通过 VariantProps 提取类型。
Tailwind 中的现代 CSS 选择器:has() 选择器、group/peer 选择器、容器查询(@container/card、@md:flex-row)。
无障碍状态:使用 focus-visible、aria-invalid、disabled 等类;<span className="sr-only">Close</span> 供屏幕阅读器使用。
暗色模式:语义化 token 自动适配,如 bg-background、text-foreground、dark:bg-input/30,以及 bg-primary、bg-card、border-input、text-muted-foreground 等。
七、决策表(摘要) 何时使用 CVA:存在多种视觉变体、尺寸变化、复合条件时使用;一次性自定义样式或来自 props 的动态颜色则不用。
何时使用复合组件:UI 复杂且含多个语义部分、存在可选区块、各部分样式不同、各部分需共享状态时使用;简单 wrapper 不适用。
何时使用 asChild:组件需兼作链接或按钮、需在自定义元素上套用按钮样式、需与路由库集成时使用;始终渲染同一元素时不适用。
何时使用 Context:prop 层层传递超过 3 层、多个同级组件共享状态、插件/扩展架构时使用;简单父子通信不适用。
八、常见模式 表单输入(Input):包含高度、圆角、边框、内边距、focus-visible 边框与环、aria-invalid 样式、disabled 状态、placeholder 颜色与暗色模式背景等类。
对话框内容(DialogContent):通过 DialogPortal 渲染,含 DialogOverlay;内容使用固定居中的定位、背景、边框、圆角、内边距与阴影,以及 data-[state=open]/closed 的进入退出动画;可选的关闭按钮带 sr-only 文本。
带 Context 的侧边栏(SidebarProvider):通过 useIsMobile 判断移动端,useState 管理开关,useEffect 绑定 keydown 监听(b 键配合 meta/ctrl 切换),useMemo 构造包含 state、open、setOpen、isMobile 的 context 值,并在 wrapper 上设置 --sidebar-width 与 --sidebar-width-icon 变量。
九、参考文件 components.md:Button、Card、Badge、Input、Label、Textarea、Dialog 的完整实现。cva.md:复合变体、响应式变体、类型提取等 CVA 模式。patterns.md:复合组件、asChild、受控状态、Context、data-slot、has() 选择器等架构模式。
十、限制 本资料未提供收费、价格、平台支持范围或效果承诺等信息,暂未提供。