跳转至

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/uiMUIAnt DesignMantine
安装方式复制代码npm 包npm 包npm 包
自定义性完全控制主题配置主题配置主题配置
样式方案TailwindEmotionCSS-in-JSCSS-in-JS
组件数量50+60+60+80+
体积极小(按需)较大较大中等
适合场景定制化项目企业级管理后台全栈

选型建议:需要高度自定义选 shadcn/ui;企业级 Material Design 选 MUI;中后台选 Ant Design。


参考资料shadcn/ui 官网 | GitHub | 组件列表