chore: import zh skill shadcn

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:22 +08:00
commit c77e6c2d17
14 changed files with 1829 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# WeHub 来源说明
- Skill 名称:`shadcn`
- 中文类目:shadcn/ui 组件初始化与定制
- 上游仓库:`sickn33__antigravity-awesome-skills`
- 上游路径:`plugins/antigravity-awesome-skills-claude/skills/shadcn/SKILL.md`
- 上游链接:https://github.com/sickn33/antigravity-awesome-skills/blob/HEAD/plugins/antigravity-awesome-skills-claude/skills/shadcn/SKILL.md
- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理
- 原作者、版权和许可证信息以上游仓库为准
+254
View File
@@ -0,0 +1,254 @@
---
name: shadcn
description: 管理 shadcn/ui 组件和项目,提供上下文、文档和使用模式,用于构建现代设计系统。
user-invocable: false
risk: safe
source: https://github.com/shadcn-ui/ui/tree/main/skills/shadcn
date_added: "2026-03-07"
---
# shadcn/ui
一个用于构建 UI、组件和设计系统的框架。组件通过 CLI 以源代码形式添加到用户项目中。
> **重要提示:** 使用项目的包运行器执行所有 CLI 命令:`npx shadcn@latest`、`pnpm dlx shadcn@latest` 或 `bunx --bun shadcn@latest`——基于项目的 `packageManager`。以下示例使用 `npx shadcn@latest`,但请替换为项目对应的正确运行器。
## 何时使用
- 当从 shadcn/ui 或社区注册表添加新组件时使用。
- 当对现有 shadcn/ui 组件进行样式设置、组合或调试时使用。
- 当初始化新项目或切换设计系统预设时使用。
- 用于获取组件文档、示例和 API 参考。
## 当前项目上下文
```json
!`npx shadcn@latest info --json 2>/dev/null || echo '{"error": "No shadcn project found. Run shadcn init first."}'`
```
上述 JSON 包含项目配置和已安装的组件。使用 `npx shadcn@latest docs <component>` 获取任意组件的文档和示例 URL。
## 原则
1. **优先使用现有组件。** 在编写自定义 UI 之前,使用 `npx shadcn@latest search` 检查注册表。同时也要检查社区注册表。
2. **组合,不要重新发明。** 设置页面 = Tabs + Card + 表单控件。仪表板 = Sidebar + Card + Chart + Table。
3. **优先使用内置变体,而非自定义样式。** 例如 `variant="outline"``size="sm"` 等。
4. **使用语义化颜色。** 例如 `bg-primary``text-muted-foreground`——绝不使用 `bg-blue-500` 这样的原始值。
## 关键规则
以下规则**始终强制执行**。每条规则链接到一个包含错误/正确代码对的文件。
### 样式与 Tailwind → [styling.md](./rules/styling.md)
- **`className` 用于布局,而非样式。** 切勿覆盖组件的颜色或排版。
- **不要使用 `space-x-*``space-y-*`。** 使用 `flex` 配合 `gap-*`。对于垂直堆叠,使用 `flex flex-col gap-*`
- **当宽度和高度相等时使用 `size-*`。** 使用 `size-10` 而非 `w-10 h-10`
- **使用 `truncate` 简写。** 而非 `overflow-hidden text-ellipsis whitespace-nowrap`
- **不要手动使用 `dark:` 颜色覆盖。** 使用语义化令牌(`bg-background``text-muted-foreground`)。
- **使用 `cn()` 处理条件类名。** 不要手写模板字面量三元表达式。
- **覆盖类组件上不要手动设置 `z-index`。** Dialog、Sheet、Popover 等会自行处理层级。
### 表单与输入 → [forms.md](./rules/forms.md)
- **表单使用 `FieldGroup` + `Field`。** 切勿在表单布局中使用原始的 `div` 配合 `space-y-*``grid gap-*`
- **`InputGroup` 使用 `InputGroupInput`/`InputGroupTextarea`。** 切勿在 `InputGroup` 内部使用原始的 `Input`/`Textarea`
- **输入框内的按钮使用 `InputGroup` + `InputGroupAddon`。**
- **选项集(27 个选项)使用 `ToggleGroup`。** 不要循环使用 `Button` 并手动维护激活状态。
- **使用 `FieldSet` + `FieldLegend` 对相关复选框/单选框进行分组。** 不要使用带标题的 `div`
- **字段验证使用 `data-invalid` + `aria-invalid`。** 在 `Field` 上使用 `data-invalid`,在控件上使用 `aria-invalid`。对于禁用状态:在 `Field` 上使用 `data-disabled`,在控件上使用 `disabled`
### 组件结构 → [composition.md](./rules/composition.md)
- **项始终放在其 Group 内部。** `SelectItem``SelectGroup``DropdownMenuItem``DropdownMenuGroup``CommandItem``CommandGroup`
- **使用 `asChild`radix)或 `render`(base)实现自定义触发器。** 从 `npx shadcn@latest info` 检查 `base` 字段。→ [base-vs-radix.md](./rules/base-vs-radix.md)
- **Dialog、Sheet 和 Drawer 始终需要 Title。** `DialogTitle``SheetTitle``DrawerTitle` 对无障碍访问是必需的。如果视觉上需要隐藏,使用 `className="sr-only"`
- **使用完整的 Card 组合。** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`。不要把所有内容都塞进 `CardContent`
- **Button 没有 `isPending`/`isLoading`。** 使用 `Spinner` + `data-icon` + `disabled` 组合。
- **`TabsTrigger` 必须放在 `TabsList` 内部。** 切勿直接在 `Tabs` 中渲染触发器。
- **`Avatar` 始终需要 `AvatarFallback`。** 用于图片加载失败的情况。
### 使用组件,而非自定义标记 → [composition.md](./rules/composition.md)
- **在编写自定义标记之前,优先使用现有组件。** 在编写带样式的 `div` 之前,先检查是否存在可用的组件。
- **提示/标注使用 `Alert`。** 不要构建自定义样式的 div。
- **空状态使用 `Empty`。** 不要构建自定义的空状态标记。
- **Toast 通过 `sonner` 实现。** 使用 `sonner` 中的 `toast()` 函数。
- **使用 `Separator`** 替代 `<hr>``<div className="border-t">`
- **使用 `Skeleton`** 作为加载占位符。不要使用自定义的 `animate-pulse` div。
- **使用 `Badge`** 替代自定义样式的 span。
### 图标 → [icons.md](./rules/icons.md)
- **`Button` 中的图标使用 `data-icon`。** 在图标上使用 `data-icon="inline-start"``data-icon="inline-end"`
- **组件内部的图标不要添加尺寸类。** 组件通过 CSS 处理图标尺寸。不要使用 `size-4``w-4 h-4`
- **将图标作为对象传递,而非字符串键。** 使用 `icon={CheckIcon}`,而不是字符串查找。
### CLI
- **切勿手动解码或获取预设代码。** 直接将其传递给 `npx shadcn@latest init --preset <code>`
## 关键模式
以下是区分正确 shadcn/ui 代码的最常见模式。对于边界情况,请参见上面链接的规则文件。
```tsx
// 表单布局:使用 FieldGroup + Field,而非 div + Label。
<FieldGroup>
<Field>
<FieldLabel htmlFor="email"></FieldLabel>
<Input id="email" />
</Field>
</FieldGroup>
// 验证:在 Field 上使用 data-invalid,在控件上使用 aria-invalid。
<Field data-invalid>
<FieldLabel></FieldLabel>
<Input aria-invalid />
<FieldDescription></FieldDescription>
</Field>
// 按钮中的图标:使用 data-icon,不要添加尺寸类。
<Button>
<SearchIcon data-icon="inline-start" />
</Button>
// 间距:使用 gap-*,而非 space-y-*。
<div className="flex flex-col gap-4"> // 正确
<div className="space-y-4"> // 错误
// 相等尺寸:使用 size-*,而非 w-* h-*。
<Avatar className="size-10"> // 正确
<Avatar className="w-10 h-10"> // 错误
// 状态颜色:使用 Badge 变体或语义化令牌,而非原始颜色。
<Badge variant="secondary">+20.1%</Badge> // 正确
<span className="text-emerald-600">+20.1%</span> // 错误
```
## 组件选择
| 需求 | 使用 |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| 按钮/操作 | `Button`,配合适当的 variant |
| 表单输入 | `Input``Select``Combobox``Switch``Checkbox``RadioGroup``Textarea``InputOTP``Slider` |
| 在 2–5 个选项之间切换 | `ToggleGroup` + `ToggleGroupItem` |
| 数据展示 | `Table``Card``Badge``Avatar` |
| 导航 | `Sidebar``NavigationMenu``Breadcrumb``Tabs``Pagination` |
| 覆盖层 | `Dialog`(模态框)、`Sheet`(侧面板)、`Drawer`(底部面板)、`AlertDialog`(确认弹窗) |
| 反馈 | `sonner`Toast)、`Alert``Progress``Skeleton``Spinner` |
| 命令面板 | `Dialog` 内部的 `Command` |
| 图表 | `Chart`(封装 Recharts |
| 布局 | `Card``Separator``Resizable``ScrollArea``Accordion``Collapsible` |
| 空状态 | `Empty` |
| 菜单 | `DropdownMenu``ContextMenu``Menubar` |
| 工具提示/信息 | `Tooltip``HoverCard``Popover` |
## 关键字段
注入的项目上下文包含以下关键字段:
- **`aliases`** → 为导入使用实际的别名前缀(例如 `@/``~/`),切勿硬编码。
- **`isRSC`** → 当为 `true` 时,使用 `useState``useEffect`、事件处理器或浏览器 API 的组件需要在文件顶部添加 `"use client"`。在就此指令提供建议时,始终参考此字段。
- **`tailwindVersion`** → `"v4"` 使用 `@theme inline` 块;`"v3"` 使用 `tailwind.config.js`
- **`tailwindCssFile`** → 定义自定义 CSS 变量的全局 CSS 文件。始终编辑此文件,切勿新建。
- **`style`** → 组件视觉风格(例如 `nova``vega`)。
- **`base`** → 底层基础库(`radix``base`)。影响组件 API 和可用属性。
- **`iconLibrary`** → 决定图标的导入来源。`lucide` 使用 `lucide-react``tabler` 使用 `@tabler/icons-react`,以此类推。切勿假设使用 `lucide-react`
- **`resolvedPaths`** → 组件、工具函数、Hook 等的精确文件系统目标路径。
- **`framework`** → 路由和文件约定(例如 Next.js App Router 与 Vite SPA)。
- **`packageManager`** → 用于安装任何非 shadcn 依赖(例如 `pnpm add date-fns``npm install date-fns`)。
完整字段参考请参见 [cli.md — `info` 命令](./cli.md)。
## 组件文档、示例与用法
运行 `npx shadcn@latest docs <component>` 获取组件的文档、示例和 API 参考的 URL。获取这些 URL 以查看实际内容。
```bash
npx shadcn@latest docs button dialog select
```
**在创建、修复、调试或使用组件时,始终先运行 `npx shadcn@latest docs` 并获取 URL。** 这确保你使用的是正确的 API 和使用模式,而非凭空猜测。
## 工作流程
1. **获取项目上下文**——已在上方注入。如果需要刷新,再次运行 `npx shadcn@latest info`
2. **首先检查已安装的组件**——在运行 `add` 之前,始终检查项目上下文中的 `components` 列表,或列出 `resolvedPaths.ui` 目录。不要导入尚未添加的组件,也不要重复添加已安装的组件。
3. **查找组件**——`npx shadcn@latest search`
4. **获取文档和示例**——运行 `npx shadcn@latest docs <component>` 获取 URL,然后获取这些 URL 的内容。使用 `npx shadcn@latest view` 浏览尚未安装的注册表项。要预览对已安装组件的更改,使用 `npx shadcn@latest add --diff`
5. **安装或更新**——`npx shadcn@latest add`。更新现有组件时,先使用 `--dry-run``--diff` 预览更改(请参见下文[更新组件](#updating-components))。
6. **修复第三方组件中的导入路径**——从社区注册表(例如 `@bundui``@magicui`)添加组件后,检查新增的非 UI 文件中是否存在硬编码的导入路径(如 `@/components/ui/...`)。这些路径可能与项目实际的别名不匹配。使用 `npx shadcn@latest info` 获取正确的 `ui` 别名(例如 `@workspace/ui/components`)并相应重写导入。CLI 会为其自己的 UI 文件重写导入路径,但第三方注册表组件可能使用与项目不匹配的默认路径。
7. **审查新增的组件**——从任何注册表添加组件或块之后,**始终读取新增的文件并验证其正确性**。检查是否缺少子组件(例如 `SelectItem` 没有 `SelectGroup`)、缺少导入、组合不正确或违反[关键规则](#关键规则)。同时将所有图标导入替换为项目上下文中的 `iconLibrary`(例如,如果注册表项使用 `lucide-react` 但项目使用 `hugeicons`,则相应替换导入和图标名称)。解决所有问题后再继续。
8. **注册表必须明确指定**——当用户要求添加块或组件时,**不要猜测注册表**。如果没有指定注册表(例如用户说"添加一个登录块"但没有指定 `@shadcn``@tailark` 等),询问使用哪个注册表。切勿代表用户默认选择注册表。
9. **切换预设**——先询问用户:**重新安装(reinstall**、**合并(merge** 还是 **跳过(skip**
- **重新安装**`npx shadcn@latest init --preset <code> --force --reinstall`。覆盖所有组件。
- **合并**`npx shadcn@latest init --preset <code> --force --no-reinstall`,然后运行 `npx shadcn@latest info` 列出已安装的组件,再对每个已安装组件分别使用 `--dry-run``--diff` 进行[智能合并](#updating-components)。
- **跳过**`npx shadcn@latest init --preset <code> --force --no-reinstall`。仅更新配置和 CSS,组件保持不变。
## 更新组件
当用户要求从上游更新组件同时保留本地更改时,使用 `--dry-run``--diff` 进行智能合并。**切勿手动从 GitHub 获取原始文件——始终使用 CLI。**
1. 运行 `npx shadcn@latest add <component> --dry-run` 查看所有会受到影响的文件。
2. 对每个文件,运行 `npx shadcn@latest add <component> --diff <file>` 查看上游与本地之间的差异。
3. 根据差异逐文件决定:
- 无本地更改→可安全覆盖。
- 有本地更改→读取本地文件,分析差异,在保留本地修改的同时应用上游更新。
- 用户说"直接更新所有内容"→使用 `--overwrite`,但先确认。
4. **未经用户明确批准,切勿使用 `--overwrite`。**
## 快速参考
```bash
# 创建新项目。
npx shadcn@latest init --name my-app --preset base-nova
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
# 创建单体仓库项目。
npx shadcn@latest init --name my-app --preset base-nova --monorepo
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
# 初始化现有项目。
npx shadcn@latest init --preset base-nova
npx shadcn@latest init --defaults # 快捷方式:--template=next --preset=base-nova
# 添加组件。
npx shadcn@latest add button card dialog
npx shadcn@latest add @magicui/shimmer-button
npx shadcn@latest add --all
# 在添加/更新之前预览更改。
npx shadcn@latest add button --dry-run
npx shadcn@latest add button --diff button.tsx
npx shadcn@latest add @acme/form --view button.tsx
# 搜索注册表。
npx shadcn@latest search @shadcn -q "sidebar"
npx shadcn@latest search @tailark -q "stats"
# 获取组件文档和示例 URL。
npx shadcn@latest docs button dialog select
# 查看注册表项详情(针对尚未安装的项)。
npx shadcn@latest view @shadcn/button
```
**命名预设:** `base-nova``radix-nova`
**模板:** `next``vite``start``react-router``astro`(均支持 `--monorepo`)和 `laravel`(不支持单体仓库)
**预设代码:**`a` 开头的 Base62 字符串(例如 `a2r6bw`),来自 [ui.shadcn.com](https://ui.shadcn.com)。
## 详细参考
- [rules/forms.md](./rules/forms.md) — FieldGroup、Field、InputGroup、ToggleGroup、FieldSet、验证状态
- [rules/composition.md](./rules/composition.md) — Groups、覆盖层、Card、Tabs、Avatar、Alert、Empty、Toast、Separator、Skeleton、Badge、Button 加载状态
- [rules/icons.md](./rules/icons.md) — data-icon、图标尺寸、将图标作为对象传递
- [rules/styling.md](./rules/styling.md) — 语义化颜色、变体、className、间距、尺寸、truncate、深色模式、cn()、z-index
- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild 与 render、Select、ToggleGroup、Slider、Accordion
- [cli.md](./cli.md) — 命令、标志、预设、模板
- [customization.md](./customization.md) — 主题化、CSS 变量、扩展组件
## 限制
- 仅当任务明确符合上述范围时使用此技能。
- 请勿将输出视为环境特定验证、测试或专家审查的替代方案。
- 如果缺少必要的输入、权限、安全边界或成功标准,请停下来并要求澄清。
+5
View File
@@ -0,0 +1,5 @@
interface:
display_name: "shadcn/ui"
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
icon_small: "./assets/shadcn-small.png"
icon_large: "./assets/shadcn.png"
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+255
View File
@@ -0,0 +1,255 @@
# shadcn CLI 参考
配置从 `components.json` 中读取。
> **重要说明:** 始终使用项目的包管理器来运行命令:`npx shadcn@latest`、`pnpm dlx shadcn@latest` 或 `bunx --bun shadcn@latest`。从项目上下文中检查 `packageManager` 以选择正确的命令。以下示例使用 `npx shadcn@latest`,但请替换为项目对应的正确运行器。
> **重要说明:** 只使用下面列出的标志。不要发明或猜测标志——如果某个标志未在此处列出,则它不存在。CLI 会从项目的锁定文件中自动检测包管理器;没有 `--package-manager` 标志。
## 目录
- 命令:init、add(空跑、智能合并)、search、view、docs、info、build
- 模板:next、vite、start、react-router、astro
- 预设:命名、代码、URL 格式与字段
- 切换预设
---
## 命令
### `init` — 初始化或创建项目
```bash
npx shadcn@latest init [components...] [options]
```
在已有项目中初始化 shadcn/ui,或创建新项目(当提供了 `--name` 时)。可选地在同一步骤中安装组件。
| 标志 | 缩写 | 描述 | 默认值 |
| ------------------------ | --- | -------------------------------------------------- | ------- |
| `--template <template>` | `-t` | 模板(next、start、vite、next-monorepo、react-router | — |
| `--preset [name]` | `-p` | 预设配置(命名、代码或 URL) | — |
| `--yes` | `-y` | 跳过确认提示 | `true` |
| `--defaults` | `-d` | 使用默认值(`--template=next --preset=base-nova` | `false` |
| `--force` | `-f` | 强制覆盖现有配置 | `false` |
| `--cwd <cwd>` | `-c` | 工作目录 | 当前目录 |
| `--name <name>` | `-n` | 新项目的名称 | — |
| `--silent` | `-s` | 静默输出 | `false` |
| `--rtl` | | 启用 RTL 支持 | — |
| `--reinstall` | | 重新安装现有 UI 组件 | `false` |
| `--monorepo` | | 搭建 monorepo 项目骨架 | — |
| `--no-monorepo` | | 跳过 monorepo 提示 | — |
`npx shadcn@latest create``npx shadcn@latest init` 的别名。
### `add` — 添加组件
> **重要说明:** 要比较本地组件与上游版本或预览更改,请**始终**使用 `npx shadcn@latest add <component> --dry-run`、`--diff` 或 `--view`。**切勿**从 GitHub 或其他来源手动获取原始文件。CLI 会自动处理注册表解析、文件路径和 CSS 差异比较。
```bash
npx shadcn@latest add [components...] [options]
```
接受组件名称、注册表前缀名称(`@magicui/shimmer-button`)、URL 或本地路径。
| 标志 | 缩写 | 描述 | 默认值 |
| ---------------------- | --- | ------------------------------------------------------------------------------- | --- |
| `--yes` | `-y` | 跳过确认提示 | `false` |
| `--overwrite` | `-o` | 覆盖现有文件 | `false` |
| `--cwd <cwd>` | `-c` | 工作目录 | 当前目录 |
| `--all` | `-a` | 添加所有可用组件 | `false` |
| `--path <path>` | `-p` | 组件的目标路径 | — |
| `--silent` | `-s` | 静默输出 | `false` |
| `--dry-run` | | 预览所有更改但不写入文件 | `false` |
| `--diff [path]` | | 显示差异。不带路径时显示前 5 个文件;带路径时只显示该文件(隐含 `--dry-run`) | — |
| `--view [path]` | | 显示文件内容。不带路径时显示前 5 个文件;带路径时只显示该文件(隐含 `--dry-run`) | — |
#### 空跑模式
使用 `--dry-run` 预览 `add` 命令会做什么,而不实际写入任何文件。`--diff``--view` 都隐含 `--dry-run`
```bash
# 预览所有更改。
npx shadcn@latest add button --dry-run
# 显示所有文件的差异(前 5 个)。
npx shadcn@latest add button --diff
# 显示特定文件的差异。
npx shadcn@latest add button --diff button.tsx
# 显示所有文件的内容(前 5 个)。
npx shadcn@latest add button --view
# 显示特定文件的完整内容。
npx shadcn@latest add button --view button.tsx
# URL 也同样支持。
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
# CSS 差异。
npx shadcn@latest add button --diff globals.css
```
**何时使用空跑模式:**
- 当用户询问「这会添加哪些文件?」或「这会更改什么?」时——使用 `--dry-run`
- 在覆盖现有组件之前——使用 `--diff` 先预览更改。
- 当用户想在不安装的情况下查看组件源码时——使用 `--view`
- 当需要检查 `globals.css` 会有哪些 CSS 更改时——使用 `--diff globals.css`
- 当用户要求在安装前审查或审核第三方注册表代码时——使用 `--view` 检查源代码。
> **`npx shadcn@latest add --dry-run` 与 `npx shadcn@latest view`** 当用户想要预览更改对其项目的影响时,优先使用 `npx shadcn@latest add --dry-run/--diff/--view`,而不是 `npx shadcn@latest view`。`npx shadcn@latest view` 只显示原始注册表元数据。`npx shadcn@latest add --dry-run` 则精确显示在用户项目中会发生什么:解析后的文件路径、与现有文件的差异、以及 CSS 更新。只有当用户想在无项目上下文的情况下浏览注册表信息时,才使用 `npx shadcn@latest view`。
#### 上游智能合并
有关完整的工作流程,请参见 [SKILL.md 中的更新组件](./SKILL.md#updating-components)。
### `search` — 搜索注册表
```bash
npx shadcn@latest search <registries...> [options]
```
跨注册表进行模糊搜索。也支持别名 `npx shadcn@latest list`。不使用 `-q` 时列出所有项目。
| 标志 | 缩写 | 描述 | 默认值 |
| ------------------------ | --- | ------------ | --- |
| `--query <query>` | `-q` | 搜索查询 | — |
| `--limit <number>` | `-l` | 每个注册表的最大项目数 | `100` |
| `--offset <number>` | `-o` | 跳过的项目数 | `0` |
| `--cwd <cwd>` | `-c` | 工作目录 | 当前目录 |
### `view` — 查看项目详情
```bash
npx shadcn@latest view <items...> [options]
```
显示项目信息,包括文件内容。示例:`npx shadcn@latest view @shadcn/button`
### `docs` — 获取组件文档 URL
```bash
npx shadcn@latest docs <components...> [options]
```
输出组件文档、示例和 API 参考的解析后 URL。接受一个或多个组件名称。获取这些 URL 以得到实际内容。
`npx shadcn@latest docs input button` 的输出示例:
```
base radix
input
docs https://ui.shadcn.com/docs/components/radix/input
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
button
docs https://ui.shadcn.com/docs/components/radix/button
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
```
某些组件包含指向底层库的 `api` 链接(例如 command 组件指向 `cmdk`)。
### `diff` — 检查更新
不要使用此命令。请改用 `npx shadcn@latest add --diff`
### `info` — 项目信息
```bash
npx shadcn@latest info [options]
```
显示项目信息和 `components.json` 配置。首先运行此命令以发现项目的框架、别名、Tailwind 版本和解析路径。
| 标志 | 缩写 | 描述 | 默认值 |
| ---------------------- | --- | ------ | --- |
| `--cwd <cwd>` | `-c` | 工作目录 | 当前目录 |
**项目信息字段:**
| 字段 | 类型 | 含义 |
| -------------------- | --------- | --------------------------------------------------------- |
| `framework` | `string` | 检测到的框架(`next``vite``react-router``start` 等) |
| `frameworkVersion` | `string` | 框架版本(例如 `15.2.4`) |
| `isSrcDir` | `boolean` | 项目是否使用 `src/` 目录 |
| `isRSC` | `boolean` | 是否启用了 React Server Components |
| `isTsx` | `boolean` | 项目是否使用 TypeScript |
| `tailwindVersion` | `string` | `"v3"``"v4"` |
| `tailwindConfigFile` | `string` | Tailwind 配置文件路径 |
| `tailwindCssFile` | `string` | 全局 CSS 文件路径 |
| `aliasPrefix` | `string` | 导入别名前缀(例如 `@``~``@/`) |
| `packageManager` | `string` | 检测到的包管理器(`npm``pnpm``yarn``bun`) |
**Components.json 字段:**
| 字段 | 类型 | 含义 |
| ----------------------- | --------- | ----------------------------------------------------- |
| `base` | `string` | 基础库(`radix``base`)——决定组件 API 和可用属性 |
| `style` | `string` | 视觉风格(例如 `nova``vega`) |
| `rsc` | `boolean` | 来自配置的 RSC 标志 |
| `tsx` | `boolean` | TypeScript 标志 |
| `tailwind.config` | `string` | Tailwind 配置路径 |
| `tailwind.css` | `string` | 全局 CSS 路径——这是放置自定义 CSS 变量的位置 |
| `iconLibrary` | `string` | 图标库——决定图标导入包(例如 `lucide-react``@tabler/icons-react` |
| `aliases.components` | `string` | 组件导入别名(例如 `@/components`) |
| `aliases.utils` | `string` | 工具导入别名(例如 `@/lib/utils`) |
| `aliases.ui` | `string` | UI 组件别名(例如 `@/components/ui`) |
| `aliases.lib` | `string` | Lib 别名(例如 `@/lib`) |
| `aliases.hooks` | `string` | Hooks 别名(例如 `@/hooks`) |
| `resolvedPaths` | `object` | 每个别名的绝对文件系统路径 |
| `registries` | `object` | 配置的自定义注册表 |
**链接字段:**
`info` 输出包含一个**链接**部分,其中包含组件文档、源代码和示例的模板化 URL。对于解析后的 URL,请改用 `npx shadcn@latest docs <component>`
### `build` — 构建自定义注册表
```bash
npx shadcn@latest build [registry] [options]
```
`registry.json` 构建为单个 JSON 文件以进行分发。默认输入:`./registry.json`,默认输出:`./public/r`
| 标志 | 缩写 | 描述 | 默认值 |
| ------------------------ | --- | ------ | ------------ |
| `--output <path>` | `-o` | 输出目录 | `./public/r` |
| `--cwd <cwd>` | `-c` | 工作目录 | 当前目录 |
---
## 模板
| 值 | 框架 | 支持 Monorepo |
| --------------- | --------------- | ----------- |
| `next` | Next.js | 是 |
| `vite` | Vite | 是 |
| `start` | TanStack Start | 是 |
| `react-router` | React Router | 是 |
| `astro` | Astro | 是 |
| `laravel` | Laravel | 否 |
所有模板均通过 `--monorepo` 标志支持 monorepo 脚手架。当传入该标志时,CLI 使用 monorepo 专用的模板目录(例如 `next-monorepo``vite-monorepo`)。当既不传递 `--monorepo` 也不传递 `--no-monorepo` 时,CLI 会交互式地提示。Laravel 不支持 monorepo 脚手架。
---
## 预设
通过 `--preset` 指定预设的三种方式:
1. **命名:** `--preset base-nova``--preset radix-nova`
2. **代码:** `--preset a2r6bw`base62 字符串,以小写字母 `a` 开头)
3. **URL** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
> **重要说明:** 切勿尝试手动解码、获取或解析预设代码。预设代码是不透明的——直接将它们传给 `npx shadcn@latest init --preset <code>`,让 CLI 处理解析。
## 切换预设
先询问用户:**重新安装**、**合并**还是**跳过**现有组件?
- **重新安装** → `npx shadcn@latest init --preset <code> --force --reinstall`。用新的预设样式覆盖所有组件文件。在用户未自定义过组件时使用。
- **合并** → `npx shadcn@latest init --preset <code> --force --no-reinstall`,然后运行 `npx shadcn@latest info` 获取已安装组件列表,并使用[智能合并工作流程](./SKILL.md#updating-components)逐一更新它们,同时保留本地更改。在用户已自定义过组件时使用。
- **跳过** → `npx shadcn@latest init --preset <code> --force --no-reinstall`。仅更新配置和 CSS 变量,保持现有组件不变。
+209
View File
@@ -0,0 +1,209 @@
---
name: customization-theming
description: shadcn/ui 定制与主题系统文档
metadata:
type: reference
---
# 定制与主题
组件引用语义化的 CSS 变量令牌。修改变量即可更改所有组件。
## 目录
- 工作原理(CSS 变量 → Tailwind 工具类 → 组件)
- 颜色变量与 OKLCH 格式
- 深色模式设置
- 更改主题(预设、CSS 变量)
- 添加自定义颜色(Tailwind v3 和 v4
- 边框圆角
- 定制组件(变体、className、包装器)
- 检查更新
---
## 工作原理
1.`:root`(浅色)和 `.dark`(深色模式)中定义 CSS 变量。
2. Tailwind 将其映射为工具类:`bg-primary``text-muted-foreground` 等。
3. 组件使用这些工具类——更改某个变量即可更改所有引用它的组件。
---
## 颜色变量
每种颜色遵循 `name` / `name-foreground` 命名约定。基础变量用于背景,`-foreground` 用于该背景上的文本/图标。
| 变量 | 用途 |
| -------------------------------------------- | -------------------------------- |
| `--background` / `--foreground` | 页面背景与默认文本 |
| `--card` / `--card-foreground` | 卡片表面 |
| `--primary` / `--primary-foreground` | 主要按钮与操作 |
| `--secondary` / `--secondary-foreground` | 次要操作 |
| `--muted` / `--muted-foreground` | 弱化/禁用状态 |
| `--accent` / `--accent-foreground` | 悬停与强调状态 |
| `--destructive` / `--destructive-foreground` | 错误与破坏性操作 |
| `--border` | 默认边框颜色 |
| `--input` | 表单输入边框 |
| `--ring` | 聚焦环颜色 |
| `--chart-1``--chart-5` | 图表/数据可视化 |
| `--sidebar-*` | 侧边栏专用颜色 |
| `--surface` / `--surface-foreground` | 次级表面 |
颜色使用 OKLCH 格式:`--primary: oklch(0.205 0 0)`,其中各值分别为明度(0–1)、色度(0 = 灰色)和色相(0–360)。
---
## 深色模式
通过根元素上的 `.dark` 类进行切换。在 Next.js 中,使用 `next-themes`
```tsx
import { ThemeProvider } from "next-themes"
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
```
---
## 更改主题
```bash
# 应用来自 ui.shadcn.com 的预设代码。
npx shadcn@latest init --preset a2r6bw --force
# 切换到命名预设。
npx shadcn@latest init --preset radix-nova --force
npx shadcn@latest init --reinstall # 更新现有组件以匹配
# 使用自定义主题 URL。
npx shadcn@latest init --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..." --force
```
或者直接在 `globals.css` 中编辑 CSS 变量。
---
## 添加自定义颜色
将变量添加到 `npx shadcn@latest info` 返回的 `tailwindCssFile` 所指向的文件中(通常为 `globals.css`)。切勿为此创建新的 CSS 文件。
```css
/* 1. 在全局 CSS 文件中定义。 */
:root {
--warning: oklch(0.84 0.16 84);
--warning-foreground: oklch(0.28 0.07 46);
}
.dark {
--warning: oklch(0.41 0.11 46);
--warning-foreground: oklch(0.99 0.02 95);
}
```
```css
/* 2a. 在 Tailwind v4 中注册(@theme inline)。 */
@theme inline {
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
}
```
`tailwindVersion``"v3"` 时(通过 `npx shadcn@latest info` 检查),改为在 `tailwind.config.js` 中注册:
```js
// 2b. 在 Tailwind v3 中注册(tailwind.config.js)。
module.exports = {
theme: {
extend: {
colors: {
warning: "oklch(var(--warning) / <alpha-value>)",
"warning-foreground":
"oklch(var(--warning-foreground) / <alpha-value>)",
},
},
},
}
```
```tsx
// 3. 在组件中使用。
<div className="bg-warning text-warning-foreground">Warning</div>
```
---
## 边框圆角
`--radius` 全局控制边框圆角。组件从其派生值(`rounded-lg` = `var(--radius)``rounded-md` = `calc(var(--radius) - 2px)`)。
---
## 定制组件
另请参阅:[rules/styling.md](./rules/styling.md) 中的错误/正确示例。
按以下顺序优先选择这些方法:
### 1. 内置变体
```tsx
<Button variant="outline" size="sm">Click</Button>
```
### 2. 通过 `className` 使用 Tailwind 类
```tsx
<Card className="max-w-md mx-auto">...</Card>
```
### 3. 添加新变体
编辑组件源码,通过 `cva` 添加变体:
```tsx
// components/ui/button.tsx
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
```
### 4. 包装器组件
将 shadcn/ui 基础组件组合成更高级的组件:
```tsx
export function ConfirmDialog({ title, description, onConfirm, children }) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
```
---
## 检查更新
```bash
npx shadcn@latest add button --diff
```
要在更新前预览具体变化,请使用 `--dry-run``--diff`
```bash
npx shadcn@latest add button --dry-run # 查看所有受影响的文件
npx shadcn@latest add button --diff button.tsx # 查看特定文件的差异
```
完整智能合并工作流请参阅 [SKILL.md 中的更新组件](./SKILL.md#updating-components)。
+47
View File
@@ -0,0 +1,47 @@
{
"skill_name": "shadcn",
"evals": [
{
"id": 1,
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
"files": [],
"expectations": [
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
"No manual dark: color overrides"
]
},
{
"id": 2,
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
"files": [],
"expectations": [
"Includes DialogTitle for accessibility (visible or with sr-only class)",
"Avatar component includes AvatarFallback",
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
"Uses asChild for custom triggers (radix preset)"
]
},
{
"id": 3,
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
"files": [],
"expectations": [
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
"Uses Badge component for percentage change instead of custom styled spans",
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
"Uses gap-* instead of space-y-* or space-x-* for spacing",
"Uses size-* when width and height are equal instead of separate w-* h-*"
]
}
]
}
+94
View File
@@ -0,0 +1,94 @@
# shadcn MCP Server
CLI 包含一个 MCP 服务器,允许 AI 助手从注册表中搜索、浏览、查看和安装组件。
---
## 安装
```bash
shadcn mcp # 启动 MCP 服务器(stdio 模式)
shadcn mcp init # 为编辑器写入配置文件
```
编辑器配置文件:
| 编辑器 | 配置文件 |
|--------|------------|
| Claude Code | `.mcp.json` |
| Cursor | `.cursor/mcp.json` |
| VS Code | `.vscode/mcp.json` |
| OpenCode | `opencode.json` |
| Codex | `~/.codex/config.toml`(手动配置) |
---
## 工具
> **提示:** MCP 工具处理注册表操作(搜索、查看、安装)。对于项目配置(别名、框架、Tailwind 版本),请使用 `npx shadcn@latest info` —— 没有对应的 MCP 命令。
### `shadcn:get_project_registries`
返回 `components.json` 中的注册表名称。如果不存在 `components.json` 则报错。
**输入:**
### `shadcn:list_items_in_registries`
列出一个或多个注册表中的所有条目。
**输入:** `registries`string[]),`limit`number,可选),`offset`number,可选)
### `shadcn:search_items_in_registries`
跨注册表进行模糊搜索。
**输入:** `registries`string[]),`query`string),`limit`number,可选),`offset`number,可选)
### `shadcn:view_items_in_registries`
查看条目详情,包含完整的文件内容。
**输入:** `items`string[])—— 例如 `["@shadcn/button", "@shadcn/card"]`
### `shadcn:get_item_examples_from_registries`
查找包含源代码的使用示例和演示。
**输入:** `registries`string[]),`query`string)—— 例如 `"accordion-demo"``"button example"`
### `shadcn:get_add_command_for_items`
返回 CLI 安装命令。
**输入:** `items`string[])—— 例如 `["@shadcn/button"]`
### `shadcn:get_audit_checklist`
返回用于验证组件(导入、依赖、lint、TypeScript)的检查清单。
**输入:**
---
## 配置注册表
注册表在 `components.json` 中设置。`@shadcn` 注册表始终内置。
```json
{
"registries": {
"@acme": "https://acme.com/r/{name}.json",
"@private": {
"url": "https://private.com/r/{name}.json",
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
}
}
}
```
- 名称必须以 `@` 开头。
- URL 必须包含 `{name}`
- `${VAR}` 引用从环境变量中解析。
社区注册表索引:`https://ui.shadcn.com/r/registries.json`
+306
View File
@@ -0,0 +1,306 @@
# Base vs Radix
`base``radix` 之间的 API 差异。请通过 `npx shadcn@latest info` 检查 `base` 字段。
## 目录
- 组合方式:asChild 与 render
- 按钮/触发器作为非按钮元素
- Select(items 属性、占位符、定位、多选、对象值)
- ToggleGrouptype 与 multiple
- Slider(标量与数组)
- Accordiontype 与 defaultValue
---
## 组合方式:asChildradix)与 renderbase
Radix 使用 `asChild` 来替换默认元素。Base 使用 `render`。不要在触发器外层包裹多余元素。
**错误用法:**
```tsx
<DialogTrigger>
<div>
<Button>Open</Button>
</div>
</DialogTrigger>
```
**正确用法(radix):**
```tsx
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
```
**正确用法(base):**
```tsx
<DialogTrigger render={<Button />}>Open</DialogTrigger>
```
这适用于所有触发器和关闭组件:`DialogTrigger``SheetTrigger``AlertDialogTrigger``DropdownMenuTrigger``PopoverTrigger``TooltipTrigger``CollapsibleTrigger``DialogClose``SheetClose``NavigationMenuLink``BreadcrumbLink``SidebarMenuButton``Badge``Item`
---
## 按钮/触发器作为非按钮元素(仅 base)
`render` 将元素改为非按钮元素(`<a>``<span>`)时,需添加 `nativeButton={false}`
**错误用法(base):** 缺少 `nativeButton={false}`
```tsx
<Button render={<a href="/docs" />}>Read the docs</Button>
```
**正确用法(base):**
```tsx
<Button render={<a href="/docs" />} nativeButton={false}>
Read the docs
</Button>
```
**正确用法(radix):**
```tsx
<Button asChild>
<a href="/docs">Read the docs</a>
</Button>
```
对于 `render` 不是 `Button` 的触发器同样适用:
```tsx
// base.
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
Pick date
</PopoverTrigger>
```
---
## Select
**items 属性(仅 base)。** Base 需要在根组件上提供 `items` 属性。Radix 仅使用内联 JSX。
**错误用法(base):**
```tsx
<Select>
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
</Select>
```
**正确用法(base):**
```tsx
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
]
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
```
**正确用法(radix):**
```tsx
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
```
**占位符。** Base 在 items 数组中使用 `{ value: null }` 项。Radix 使用 `<SelectValue placeholder="...">`
**内容定位。** Base 使用 `alignItemWithTrigger`。Radix 使用 `position`
```tsx
// base.
<SelectContent alignItemWithTrigger={false} side="bottom">
// radix.
<SelectContent position="popper">
```
---
## Select——多选与对象值(仅 base)
Base 支持 `multiple``SelectValue` 上的 render 函数子元素,以及通过 `itemToStringValue` 实现的对象值。Radix 仅支持单选和字符串值。
**正确用法(base——多选):**
```tsx
<Select items={items} multiple defaultValue={[]}>
<SelectTrigger>
<SelectValue>
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
</SelectValue>
</SelectTrigger>
...
</Select>
```
**正确用法(base——对象值):**
```tsx
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
<SelectTrigger>
<SelectValue>{(value) => value.name}</SelectValue>
</SelectTrigger>
...
</Select>
```
---
## ToggleGroup
Base 使用布尔类型的 `multiple` 属性。Radix 使用 `type="single"``type="multiple"`
**错误用法(base):**
```tsx
<ToggleGroup type="single" defaultValue="daily">
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
</ToggleGroup>
```
**正确用法(base):**
```tsx
// 单选(无需属性),defaultValue 始终为数组。
<ToggleGroup defaultValue={["daily"]} spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
</ToggleGroup>
// 多选。
<ToggleGroup multiple>
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>
```
**正确用法(radix):**
```tsx
// 单选,defaultValue 为字符串。
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
</ToggleGroup>
// 多选。
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>
```
**受控模式下的单选值:**
```tsx
// base——对数组进行包裹/解包。
const [value, setValue] = React.useState("normal")
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
// radix——纯字符串。
const [value, setValue] = React.useState("normal")
<ToggleGroup type="single" value={value} onValueChange={setValue}>
```
---
## Slider
Base 对单滑块接受纯数字。Radix 始终要求使用数组。
**错误用法(base):**
```tsx
<Slider defaultValue={[50]} max={100} step={1} />
```
**正确用法(base):**
```tsx
<Slider defaultValue={50} max={100} step={1} />
```
**正确用法(radix):**
```tsx
<Slider defaultValue={[50]} max={100} step={1} />
```
两者在范围滑块上都使用数组。Base 中的受控 `onValueChange` 可能需要类型断言:
```tsx
// base.
const [value, setValue] = React.useState([0.3, 0.7])
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
// radix.
const [value, setValue] = React.useState([0.3, 0.7])
<Slider value={value} onValueChange={setValue} />
```
---
## Accordion
Radix 要求使用 `type="single"``type="multiple"`,并支持 `collapsible``defaultValue` 为字符串。Base 不使用 `type` 属性,使用布尔类型的 `multiple`,且 `defaultValue` 始终为数组。
**错误用法(base):**
```tsx
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
```
**正确用法(base):**
```tsx
<Accordion defaultValue={["item-1"]}>
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
// 多选。
<Accordion multiple defaultValue={["item-1", "item-2"]}>
<AccordionItem value="item-1">...</AccordionItem>
<AccordionItem value="item-2">...</AccordionItem>
</Accordion>
```
**正确用法(radix):**
```tsx
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
```
+195
View File
@@ -0,0 +1,195 @@
# 组件组合
## 目录
- 条目始终位于其 Group 组件内
- 标注提示使用 Alert
- 空状态使用 Empty 组件
- Toast 通知使用 sonner
- 如何选择覆盖层组件
- Dialog、Sheet 和 Drawer 始终需要 Title
- Card 结构
- Button 没有 isPending 或 isLoading 属性
- TabsTrigger 必须位于 TabsList 内部
- Avatar 始终需要 AvatarFallback
- 使用 Separator 替代原生 hr 或 border div
- 使用 Skeleton 作为加载占位符
- 使用 Badge 替代自定义样式 span
---
## 条目始终位于其 Group 组件内
切勿直接在内容容器内渲染条目。
**错误写法:**
```tsx
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectContent>
```
**正确写法:**
```tsx
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
```
这适用于所有基于 Group 的组件:
| 条目 | 分组容器 |
|------|---------|
| `SelectItem`, `SelectLabel` | `SelectGroup` |
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
| `MenubarItem` | `MenubarGroup` |
| `ContextMenuItem` | `ContextMenuGroup` |
| `CommandItem` | `CommandGroup` |
---
## 标注提示使用 Alert
```tsx
<Alert>
<AlertTitle></AlertTitle>
<AlertDescription></AlertDescription>
</Alert>
```
---
## 空状态使用 Empty 组件
```tsx
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
<EmptyTitle></EmptyTitle>
<EmptyDescription>使</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button></Button>
</EmptyContent>
</Empty>
```
---
## Toast 通知使用 sonner
```tsx
import { toast } from "sonner"
toast.success("更改已保存。")
toast.error("发生错误。")
toast("文件已删除。", {
action: { label: "撤销", onClick: () => undoDelete() },
})
```
---
## 如何选择覆盖层组件
| 使用场景 | 组件 |
|----------|------|
| 需要用户输入的专注型任务 | `Dialog` |
| 破坏性操作确认 | `AlertDialog` |
| 显示详情或筛选条件的侧面板 | `Sheet` |
| 移动端优先的底部面板 | `Drawer` |
| 悬停时快速查看信息 | `HoverCard` |
| 点击后显示小型上下文内容 | `Popover` |
---
## Dialog、Sheet 和 Drawer 始终需要 Title
`DialogTitle``SheetTitle``DrawerTitle` 是无障碍所必需的。如果希望视觉上隐藏,请使用 `className="sr-only"`
```tsx
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
...
</DialogContent>
```
---
## Card 结构
使用完整的组合方式——不要把所有内容都塞进 `CardContent`
```tsx
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>...</CardContent>
<CardFooter>
<Button></Button>
</CardFooter>
</Card>
```
---
## Button 没有 isPending 或 isLoading 属性
配合 `Spinner` + `data-icon` + `disabled` 进行组合:
```tsx
<Button disabled>
<Spinner data-icon="inline-start" />
...
</Button>
```
---
## TabsTrigger 必须位于 TabsList 内部
切勿在 `Tabs` 内部直接渲染 `TabsTrigger`——始终包裹在 `TabsList` 中:
```tsx
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account"></TabsTrigger>
<TabsTrigger value="password"></TabsTrigger>
</TabsList>
<TabsContent value="account">...</TabsContent>
</Tabs>
```
---
## Avatar 始终需要 AvatarFallback
始终包含 `AvatarFallback`,用于图片加载失败时的降级显示:
```tsx
<Avatar>
<AvatarImage src="/avatar.png" alt="用户" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
```
---
## 使用现有组件替代自定义标记
| 不要使用 | 请使用 |
|---|---|
| `<hr>``<div className="border-t">` | `<Separator />` |
| `<div className="animate-pulse">` 配合样式 div | `<Skeleton className="h-4 w-3/4" />` |
| `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
+192
View File
@@ -0,0 +1,192 @@
# 表单与输入
## 目录
- 表单使用 FieldGroup + Field
- InputGroup 需配合 InputGroupInput/InputGroupTextarea
- 输入框内的按钮使用 InputGroup + InputGroupAddon
- 选项集合(2–7 个选项)使用 ToggleGroup
- FieldSet + FieldLegend 用于对相关字段进行分组
- 字段校验与禁用状态
---
## 表单使用 FieldGroup + Field
始终使用 `FieldGroup` + `Field` —— 切勿使用 `space-y-*` 的原始 `div`
```tsx
<FieldGroup>
<Field>
<FieldLabel htmlFor="email"></FieldLabel>
<Input id="email" type="email" />
</Field>
<Field>
<FieldLabel htmlFor="password"></FieldLabel>
<Input id="password" type="password" />
</Field>
</FieldGroup>
```
在设置页面中使用 `Field orientation="horizontal"`。对于视觉上隐藏的标签,使用 `FieldLabel className="sr-only"`
**选择表单控件:**
- 简单文本输入 → `Input`
- 带预定义选项的下拉框 → `Select`
- 可搜索下拉框 → `Combobox`
- 原生 HTML 选择(无 JS)→ `native-select`
- 布尔切换 → `Switch`(用于设置)或 `Checkbox`(用于表单)
- 少数选项中的单选 → `RadioGroup`
- 在 25 个选项之间切换 → `ToggleGroup` + `ToggleGroupItem`
- OTP/验证码 → `InputOTP`
- 多行文本 → `Textarea`
---
## InputGroup 需配合 InputGroupInput/InputGroupTextarea
切勿在 `InputGroup` 内部使用原始的 `Input``Textarea`
**错误示例:**
```tsx
<InputGroup>
<Input placeholder="搜索..." />
</InputGroup>
```
**正确示例:**
```tsx
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
<InputGroup>
<InputGroupInput placeholder="搜索..." />
</InputGroup>
```
---
## 输入框内的按钮使用 InputGroup + InputGroupAddon
切勿通过自定义定位将 `Button` 直接放在 `Input` 内部或紧邻其旁。
**错误示例:**
```tsx
<div className="relative">
<Input placeholder="搜索..." className="pr-10" />
<Button className="absolute right-0 top-0" size="icon">
<SearchIcon />
</Button>
</div>
```
**正确示例:**
```tsx
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
<InputGroup>
<InputGroupInput placeholder="搜索..." />
<InputGroupAddon>
<Button size="icon">
<SearchIcon data-icon="inline-start" />
</Button>
</InputGroupAddon>
</InputGroup>
```
---
## 选项集合(27 个选项)使用 ToggleGroup
不要手动循环 `Button` 组件并自行管理激活状态。
**错误示例:**
```tsx
const [selected, setSelected] = useState("daily")
<div className="flex gap-2">
{["daily", "weekly", "monthly"].map((option) => (
<Button
key={option}
variant={selected === option ? "default" : "outline"}
onClick={() => setSelected(option)}
>
{option}
</Button>
))}
</div>
```
**正确示例:**
```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
<ToggleGroup spacing={2}>
<ToggleGroupItem value="daily"></ToggleGroupItem>
<ToggleGroupItem value="weekly"></ToggleGroupItem>
<ToggleGroupItem value="monthly"></ToggleGroupItem>
</ToggleGroup>
```
结合 `Field` 使用,创建带标签的切换组:
```tsx
<Field orientation="horizontal">
<FieldTitle id="theme-label"></FieldTitle>
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
<ToggleGroupItem value="light"></ToggleGroupItem>
<ToggleGroupItem value="dark"></ToggleGroupItem>
<ToggleGroupItem value="system"></ToggleGroupItem>
</ToggleGroup>
</Field>
```
> **注意:** `defaultValue` 和 `type`/`multiple` 属性在 base 与 radix 之间存在差异。详见 [base-vs-radix.md](./base-vs-radix.md#togglegroup)。
---
## FieldSet + FieldLegend 用于对相关字段进行分组
对相关的复选框、单选按钮或开关,使用 `FieldSet` + `FieldLegend` —— 而不是在 `div` 中加一个标题:
```tsx
<FieldSet>
<FieldLegend variant="label"></FieldLegend>
<FieldDescription></FieldDescription>
<FieldGroup className="gap-3">
<Field orientation="horizontal">
<Checkbox id="dark" />
<FieldLabel htmlFor="dark" className="font-normal"></FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
```
---
## 字段校验与禁用状态
两个属性都需要 —— `data-invalid`/`data-disabled` 用于设置字段的样式(标签、描述),而 `aria-invalid`/`disabled` 用于设置控件的样式。
```tsx
// 无效状态
<Field data-invalid>
<FieldLabel htmlFor="email"></FieldLabel>
<Input id="email" aria-invalid />
<FieldDescription></FieldDescription>
</Field>
// 禁用状态
<Field data-disabled>
<FieldLabel htmlFor="email"></FieldLabel>
<Input id="email" disabled />
</Field>
```
适用于所有控件:`Input``Textarea``Select``Checkbox``RadioGroupItem``Switch``Slider``NativeSelect``InputOTP`
+101
View File
@@ -0,0 +1,101 @@
# 图标
**始终使用项目配置的 `iconLibrary` 进行导入。** 从项目上下文中查看 `iconLibrary` 字段:`lucide``lucide-react``tabler``@tabler/icons-react`,以此类推。切勿默认使用 `lucide-react`
---
## 按钮中的图标使用 data-icon 属性
在图标上添加 `data-icon="inline-start"`(前缀)或 `data-icon="inline-end"(后缀)。不在图标上添加尺寸类。
**错误:**
```tsx
<Button>
<SearchIcon className="mr-2 size-4" />
Search
</Button>
```
**正确:**
```tsx
<Button>
<SearchIcon data-icon="inline-start"/>
Search
</Button>
<Button>
Next
<ArrowRightIcon data-icon="inline-end"/>
</Button>
```
---
## 组件内部的图标不加尺寸类
组件通过 CSS 处理图标尺寸。不要在 `Button`、`DropdownMenuItem`、`Alert`、`Sidebar*` 或其他 shadcn 组件内部的图标上添加 `size-4`、`w-4 h-4` 或其他尺寸类。除非用户明确要求自定义图标尺寸。
**错误:**
```tsx
<Button>
<SearchIcon className="size-4" data-icon="inline-start" />
Search
</Button>
<DropdownMenuItem>
<SettingsIcon className="mr-2 size-4" />
Settings
</DropdownMenuItem>
```
**正确:**
```tsx
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>
<DropdownMenuItem>
<SettingsIcon />
Settings
</DropdownMenuItem>
```
---
## 将图标作为组件对象传递,而非字符串键
使用 `icon={CheckIcon}`,而不是使用指向查找映射表的字符串键。
**错误:**
```tsx
const iconMap = {
check: CheckIcon,
alert: AlertIcon,
}
function StatusBadge({ icon }: { icon: string }) {
const Icon = iconMap[icon]
return <Icon />
}
<StatusBadge icon="check" />
```
**正确:**
```tsx
// 从项目配置的 iconLibrary 导入(例如 lucide-react、@tabler/icons-react)。
import { CheckIcon } from "lucide-react"
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
return <Icon />
}
<StatusBadge icon={CheckIcon} />
```
+162
View File
@@ -0,0 +1,162 @@
# 样式与自定义
关于主题、CSS 变量以及添加自定义颜色,请参阅 [customization.md](../customization.md)。
## 目录
- 语义化颜色
- 优先使用内置变体
- className 仅用于布局
- 避免 space-x-* / space-y-*
- 宽高相等时优先使用 size-*
- 优先使用 truncate 简写
- 不要手动覆写 dark: 颜色
- 使用 cn() 处理条件类名
- 浮层组件上不要手动设置 z-index
---
## 语义化颜色
**错误写法:**
```tsx
<div className="bg-blue-500 text-white">
<p className="text-gray-600"></p>
</div>
```
**正确写法:**
```tsx
<div className="bg-primary text-primary-foreground">
<p className="text-muted-foreground"></p>
</div>
```
---
## 状态/状态指示器不要使用原始颜色值
对于正向、负向或状态指示器,应使用 Badge 变体、`text-destructive` 等语义化标记,或定义自定义 CSS 变量——不要直接使用 Tailwind 原始颜色。
**错误写法:**
```tsx
<span className="text-emerald-600">+20.1%</span>
<span className="text-green-500"></span>
<span className="text-red-600">-3.2%</span>
```
**正确写法:**
```tsx
<Badge variant="secondary">+20.1%</Badge>
<Badge></Badge>
<span className="text-destructive">-3.2%</span>
```
如果需要的成功/正向颜色不存在对应的语义化标记,请使用 Badge 变体,或请用户向主题中添加自定义 CSS 变量(参见 [customization.md](../customization.md))。
---
## 优先使用内置变体
**错误写法:**
```tsx
<Button className="border border-input bg-transparent hover:bg-accent">
</Button>
```
**正确写法:**
```tsx
<Button variant="outline"></Button>
```
---
## className 仅用于布局
使用 `className` 处理布局(例如 `max-w-md``mx-auto``mt-4`),**不要**用它覆写组件的颜色或排版。如需修改颜色,请使用语义化标记、内置变体或 CSS 变量。
**错误写法:**
```tsx
<Card className="bg-blue-100 text-blue-900 font-bold">
<CardContent></CardContent>
</Card>
```
**正确写法:**
```tsx
<Card className="max-w-md mx-auto">
<CardContent></CardContent>
</Card>
```
若要自定义组件的外观,请按以下顺序优先选择:
1. **内置变体**——`variant="outline"``variant="destructive"` 等。
2. **语义化颜色标记**——`bg-primary``text-muted-foreground`
3. **CSS 变量**——在全局 CSS 文件中定义自定义颜色(参见 [customization.md](../customization.md))。
---
## 避免 space-x-* / space-y-*
请改用 `gap-*``space-y-4``flex flex-col gap-4``space-x-2``flex gap-2`
```tsx
<div className="flex flex-col gap-4">
<Input />
<Input />
<Button></Button>
</div>
```
---
## 宽高相等时优先使用 size-*
使用 `size-10` 而非 `w-10 h-10`。适用于图标、头像、骨架屏等场景。
---
## 优先使用 truncate 简写
使用 `truncate` 而非 `overflow-hidden text-ellipsis whitespace-nowrap`
---
## 不要手动覆写 dark: 颜色
使用语义化标记——它们通过 CSS 变量处理亮色/暗色主题。使用 `bg-background text-foreground` 而非 `bg-white dark:bg-gray-950`
---
## 使用 cn() 处理条件类名
使用项目中的 `cn()` 工具函数处理条件性或合并的类名字符串。不要在 className 字符串中手动编写三元表达式。
**错误写法:**
```tsx
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
```
**正确写法:**
```tsx
import { cn } from "@/lib/utils"
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
```
---
## 浮层组件上不要手动设置 z-index
`Dialog``Sheet``Drawer``AlertDialog``DropdownMenu``Popover``Tooltip``HoverCard` 会自行处理层叠顺序。切勿添加 `z-50``z-[999]`