CSS

shadcn/ui

📒 shadcn/ui 的作用和使用方式。

发布于 2026年5月30日0 views

shadcn/ui 不是传统意义上的组件库。

它更像一套“组件代码生成工具”:通过 CLI 把 Button、Dialog、Input 这些组件代码添加到你的项目里。添加完成后,组件文件就在你的项目源码中,可以直接修改。

它解决什么问题

做 React / Next.js 项目时,经常会重复写这些基础 UI:

  • Button
  • Input
  • Dialog
  • Dropdown
  • Tabs
  • Form

如果完全自己写,样式、交互、无障碍细节都要处理;如果使用传统组件库,组件又常常被封装在依赖包里,不太好深度修改。

shadcn/ui 的思路是:直接把一份结构清晰、默认好看的组件源码放进项目,让你既能快速开始,也能自由修改。

它的工作方式

shadcn/ui 的核心不是运行时组件库,而是 CLI。

CLI 主要负责:

  • 初始化项目配置。
  • 生成 components.json
  • 添加组件源码。
  • 安装组件需要的依赖。

比如执行:

npx shadcn@latest init

会生成类似:

components.json

再执行:

npx shadcn@latest add button

会生成类似:

src/components/ui/button.tsx

之后你在项目里使用的是本地文件:

import { Button } from "@/components/ui/button";

也就是说,运行时并不是从 shadcn/ui 这个包里 import Button,而是从你项目自己的 components/ui/button.tsx 里 import。

运行时依赖

生成后的组件代码会用到一些实际依赖。

常见依赖包括:

  • @radix-ui/react-slot:让组件可以把样式和行为传给子元素,常用于 asChild
  • class-variance-authority:管理组件变体,比如按钮的大小、颜色、样式类型。
  • clsx:按条件拼接 className。
  • tailwind-merge:合并 Tailwind class,避免冲突。
  • lucide-react:常用图标。

所以可以这样理解:

  • shadcn CLI:负责生成和添加代码。
  • components.json:记录组件生成配置。
  • src/components/ui/*:真正存在项目里的组件源码。
  • @radix-ui/*class-variance-authorityclsx 等:组件运行时真正用到的依赖。

和普通组件库的区别

普通组件库通常这样用:

import { Button } from "some-ui-library";

组件源码在依赖包里,你主要通过 props、主题配置或覆盖样式来调整。

shadcn/ui 通常这样用:

import { Button } from "@/components/ui/button";

组件源码就在项目里。你可以直接改:

  • JSX 结构。
  • Tailwind class。
  • 组件 props。
  • 变体配置。
  • 默认交互。

这也是它最重要的特点:不是安装一套黑盒组件,而是把可维护的组件源码放进项目。

基本使用方式

初始化:

npx shadcn@latest init

按需添加组件:

npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add input

使用组件:

import { Button } from "@/components/ui/button";

export function SaveButton() {
  return <Button>保存</Button>;
}

Demo:添加 Dialog 组件

以 Dialog 弹窗为例,看一下 shadcn/ui 生成组件代码的过程。

1. 执行命令

npx shadcn@latest add dialog

这条命令会做几件事:

  • 检查 components.json 配置。
  • 安装 Dialog 需要的依赖。
  • 生成 Dialog 组件源码。
  • 把组件放到你的 components/ui 目录里。

常见生成文件:

src/components/ui/dialog.tsx

常见新增依赖:

@radix-ui/react-dialog

Dialog 组件内部通常还会用到项目里的工具函数,比如:

import { cn } from "@/lib/utils";

cn 一般是 clsx + tailwind-merge 的封装,用来合并 className。

2. 生成后的组件是什么

生成的 dialog.tsx 大概会导出这些组件:

Dialog
DialogTrigger
DialogContent
DialogHeader
DialogTitle
DialogDescription
DialogFooter
DialogClose

这些组件不是从 shadcn/ui 运行时加载的,而是你项目里的本地源码。

你使用时是这样导入:

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";

3. 在页面中使用

import { Button } from "@/components/ui/button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";

export function DeleteUserDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">删除用户</Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>确认删除用户?</DialogTitle>
          <DialogDescription>
            删除后数据将无法恢复,请谨慎操作。
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  );
}

这里发生了几件事:

  • Dialog:管理弹窗打开和关闭状态。
  • DialogTrigger:触发打开弹窗。
  • asChild:让 Button 作为真正的触发元素。
  • DialogContent:弹窗主体。
  • DialogTitle / DialogDescription:提供标题、描述和无障碍语义。

4. 为什么它依赖 Radix UI

Dialog 看起来只是一个弹窗,但实际包含很多交互细节:

  • 打开后焦点进入弹窗。
  • Esc 可以关闭。
  • 点击遮罩可以关闭。
  • 关闭后焦点回到触发按钮。
  • 屏幕阅读器能识别标题和描述。

这些底层行为通常由 @radix-ui/react-dialog 处理。

shadcn/ui 生成的 dialog.tsx 主要做的是:

  • 引入 Radix Dialog 原语。
  • 包一层项目自己的组件命名。
  • 加上 Tailwind CSS 样式。
  • 暴露更适合项目使用的组件。

5. 如何修改样式

因为 dialog.tsx 已经在你的项目里,所以可以直接打开文件修改。

比如你想修改弹窗宽度,可以找到 DialogContent 里的 className:

"sm:max-w-lg"

改成:

"sm:max-w-xl"

这就是 shadcn/ui 和传统组件库最大的区别:组件源码属于你的项目,不是依赖包里的黑盒。

适合什么场景

适合:

  • React / Next.js 项目。
  • 使用 Tailwind CSS 的项目。
  • 希望快速拥有一套基础 UI。
  • 希望组件源码可以直接修改。
  • 希望和 Radix UI、lucide-react 等生态搭配使用。

不太适合:

  • 非 React 项目。
  • 不使用 Tailwind CSS 的项目。
  • 希望通过升级依赖自动更新所有组件的场景。

使用建议

不要一次性添加所有组件。

建议需要什么加什么:

npx shadcn@latest add button

项目里可以约定:

  • components/ui:放 shadcn/ui 生成的基础组件。
  • components/common:放业务无关的通用组件。
  • components/feature:放具体业务模块组件。

这样基础 UI 和业务组件会更清楚。

总结

shadcn/ui 的重点是“生成组件源码”,不是“提供运行时组件库”。CLI 负责把代码添加到项目,真正运行的是项目里的 src/components/ui/* 文件,以及这些组件用到的 Radix UI、CVA、clsx、tailwind-merge、lucide-react 等依赖。