623 shadcn/ui 组件库
一句话概述:shadcn/ui 不是传统 npm 组件库,而是"复制粘贴"模式的 React 组件集合,基于 Radix UI + Tailwind CSS,代码直接放在你的项目中,完全可定制。
核心知识点速查表
| 知识点 | 说明 |
|---|
| 设计理念 | 复制代码到项目中,不是安装依赖包 |
| 底层依赖 | Radix UI(无障碍原语)+ Tailwind CSS(样式) |
| 框架支持 | React(主)、Next.js、Remix、Astro |
| 安装方式 | npx shadcn@latest add 组件名 |
| 主题系统 | CSS 变量(支持亮色/暗色模式) |
| 适用场景 | React 管理后台、SaaS 产品、Dashboard |
一、安装配置
1.1 初始化
# 在现有 Next.js/React 项目中初始化
npx shadcn@latest init # 交互式配置(选择样式、颜色、CSS变量等)
# 初始化时会问:
# - 使用哪种样式?(Default / New York)
# - 基础颜色?(Slate / Gray / Zinc / Neutral / Stone)
# - 是否使用 CSS 变量做颜色?(推荐 Yes)
1.2 初始化后的项目结构
my-app/
├── components/
│ └── ui/ # shadcn/ui 组件目录(代码在这里)
│ ├── button.tsx # 按钮组件
│ ├── card.tsx # 卡片组件
│ └── input.tsx # 输入框组件
├── lib/
│ └── utils.ts # cn() 工具函数(合并 className)
├── components.json # shadcn/ui 配置文件
└── app/
└── globals.css # 全局样式(含 CSS 变量主题)
二、基本使用
2.1 添加组件
# 按需添加组件(每次添加一个)
npx shadcn@latest add button # 添加按钮
npx shadcn@latest add card # 添加卡片
npx shadcn@latest add input # 添加输入框
npx shadcn@latest add dialog # 添加对话框
npx shadcn@latest add table # 添加表格
npx shadcn@latest add form # 添加表单(含 react-hook-form + zod)
# 批量添加
npx shadcn@latest add button card input dialog
# 白话:运行命令后,组件代码会复制到 components/ui/ 目录
# 你可以直接修改这些文件,没有任何限制
2.2 使用按钮组件
// app/page.tsx
import { Button } from "@/components/ui/button"; // 从本地导入(不是 npm 包)
export default function Home() {
return (
<div className="p-8 space-y-4">
{/* 基础按钮变体 */}
<Button>默认按钮</Button>
<Button variant="secondary">次要按钮</Button>
<Button variant="destructive">危险按钮</Button>
<Button variant="outline">轮廓按钮</Button>
<Button variant="ghost">幽灵按钮</Button>
<Button variant="link">链接按钮</Button>
{/* 尺寸 */}
<Button size="sm">小按钮</Button>
<Button size="default">默认</Button>
<Button size="lg">大按钮</Button>
<Button size="icon">🔍</Button>
{/* 禁用和加载状态 */}
<Button disabled>禁用</Button>
<Button className="w-full">全宽按钮</Button>
</div>
);
}
2.3 卡片组件
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
export function ProjectCard() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>项目名称</CardTitle>
<CardDescription>项目的简要描述信息</CardDescription>
</CardHeader>
<CardContent>
<p>这里放主要内容,可以是表单、图表、列表等任何内容。</p>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">取消</Button>
<Button>确认</Button>
</CardFooter>
</Card>
);
}
2.4 表单与输入
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export function ContactForm() {
return (
<form className="space-y-4 max-w-md">
{/* 文本输入 */}
<div className="space-y-2">
<Label htmlFor="name">姓名</Label>
<Input id="name" placeholder="请输入姓名" />
</div>
{/* 下拉选择 */}
<div className="space-y-2">
<Label>部门</Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="选择部门" />
</SelectTrigger>
<SelectContent>
<SelectItem value="dev">开发部</SelectItem>
<SelectItem value="design">设计部</SelectItem>
<SelectItem value="pm">产品部</SelectItem>
</SelectContent>
</Select>
</div>
{/* 多行文本 */}
<div className="space-y-2">
<Label htmlFor="message">留言</Label>
<Textarea id="message" placeholder="请输入留言内容" />
</div>
</form>
);
}
三、高级用法
3.1 对话框(Dialog)
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function EditDialog() {
return (
<Dialog>
<DialogTrigger asChild>
{/* asChild:用子元素作为触发器(不额外包一层 button) */}
<Button variant="outline">编辑资料</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>编辑个人资料</DialogTitle>
<DialogDescription>
修改你的个人信息,完成后点击保存。
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">姓名</Label>
<Input id="name" defaultValue="张三" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="email" className="text-right">邮箱</Label>
<Input id="email" defaultValue="zhang@example.com" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">保存</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
3.2 数据表格(Data Table)
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
// 模拟数据
const users = [
{ id: 1, name: "张三", role: "管理员", status: "active" },
{ id: 2, name: "李四", role: "编辑", status: "active" },
{ id: 3, name: "王五", role: "访客", status: "inactive" },
];
export function UserTable() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>姓名</TableHead>
<TableHead>角色</TableHead>
<TableHead>状态</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell>
<Badge variant={user.status === "active" ? "default" : "secondary"}>
{user.status === "active" ? "活跃" : "停用"}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
3.3 自定义主题
/* app/globals.css - 修改 CSS 变量来改主题 */
@layer base {
:root {
--background: 0 0% 100%; /* 背景色(HSL格式) */
--foreground: 222.2 84% 4.9%; /* 前景色(文字) */
--primary: 221.2 83.2% 53.3%; /* 主色调 */
--primary-foreground: 210 40% 98%; /* 主色调上的文字 */
--destructive: 0 84.2% 60.2%; /* 危险色(红色) */
--radius: 0.5rem; /* 全局圆角 */
}
.dark {
--background: 222.2 84% 4.9%; /* 暗色模式背景 */
--foreground: 210 40% 98%; /* 暗色模式文字 */
--primary: 217.2 91.2% 59.8%;
}
}
/* 白话:改这些变量就能一键换主题,所有组件自动跟着变 */
3.4 cn() 工具函数
// lib/utils.ts - 合并 className 的工具
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); // 合并类名,自动解决冲突
}
// 使用示例:
// cn("px-4 py-2", condition && "bg-blue-500", "px-6")
// → "py-2 bg-blue-500 px-6" (px-6 覆盖 px-4,不冲突)
四、常见报错与解决
| 问题 | 解决 |
|---|
Module not found: @/components/ui/button | 检查 tsconfig.json 中 @/* 路径别名配置 |
| 组件样式不生效 | 确保 Tailwind CSS 已正确配置,globals.css 已导入 |
npx shadcn add 失败 | 检查 components.json 配置是否正确 |
| 暗色模式不生效 | 在 html 标签加 class="dark" 或配置 next-themes |
五、速查表
| 操作 | 命令/代码 |
|---|
| 初始化 | npx shadcn@latest init |
| 添加组件 | npx shadcn@latest add 组件名 |
| 查看所有组件 | npx shadcn@latest add --all(添加全部) |
| 更新组件 | npx shadcn@latest diff(查看差异) |
| 按钮 | <Button variant="default/secondary/destructive/outline/ghost/link"> |
| 卡片 | <Card> + CardHeader + CardContent + CardFooter |
| 输入框 | <Input placeholder="..."> |
| 对话框 | <Dialog> + DialogTrigger + DialogContent |
| 表格 | <Table> + TableHeader + TableBody + TableRow |
| 合并类名 | cn("class1", condition && "class2") |
六、同类工具对比
| 特性 | shadcn/ui | MUI | Ant Design | Mantine |
|---|
| 安装方式 | 复制代码 | npm 包 | npm 包 | npm 包 |
| 自定义性 | 完全控制 | 主题配置 | 主题配置 | 主题配置 |
| 样式方案 | Tailwind | Emotion | CSS-in-JS | CSS-in-JS |
| 组件数量 | 50+ | 60+ | 60+ | 80+ |
| 体积 | 极小(按需) | 较大 | 较大 | 中等 |
| 适合场景 | 定制化项目 | 企业级 | 管理后台 | 全栈 |
选型建议:需要高度自定义选 shadcn/ui;企业级 Material Design 选 MUI;中后台选 Ant Design。
参考资料:shadcn/ui 官网 | GitHub | 组件列表