10.1 关键概念
- Dashboard 骨架
- 典型后台三段式:Sidebar(导航)+ Header(面包屑/搜索/用户菜单)+ Main(内容区),用第 6 章的 Sidebar/Sheet 搭建。
- 组件复用策略
components/ui/放 shadcn 原语(尽量少改),components/放项目复合组件;业务组合封在功能目录,避免到处复制粘贴。- a11y(可访问性)
- Accessibility 缩写。Radix 已给行为兜底,你仍需负责:语义 HTML、Label 关联、图标按钮的
sr-only文本、对比度、焦点可见。 - sr-only
- 视觉隐藏但屏幕阅读器可读的 class,给纯图标按钮补文字标签的标准做法。
- v0.dev
- Vercel 的 AI 生成 UI 工具,产出的正是 shadcn/ui + Tailwind 代码,可直接粘进项目,与本教程技术栈无缝衔接。
- MCP / AI 工作流
- shadcn 提供 MCP server,让 Claude、Cursor 等 AI 助手能直接读 registry、装组件,把"复制源码"自动化进 AI 编码流程。
- Tree-shaking
- 因为组件是项目源码、按需 add,未使用的组件根本不进 bundle,天然按需,无整包引入负担。
10.2 Dashboard 布局骨架
// app/(dashboard)/layout.tsx
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { Separator } from "@/components/ui/separator";
import { AppSidebar } from "@/components/app-sidebar";
export default function Layout({ children }) {
return (
<SidebarProvider>
<AppSidebar />
<div className="flex-1">
<header className="flex h-14 items-center gap-2 border-b px-4">
<SidebarTrigger />
<Separator orientation="vertical" className="h-4" />
<h1 className="text-sm font-medium">仪表盘</h1>
</header>
<main className="p-4 md:p-6">{children}</main>
</div>
</SidebarProvider>
);
}
10.3 统计卡片区
// 首屏 KPI 卡片网格
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{stats.map((s) => (
<Card key={s.label}>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{s.label}</CardTitle>
<s.Icon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{s.value}</div>
<p className="text-xs text-muted-foreground">{s.delta}</p>
</CardContent>
</Card>
))}
</div>
// 下方拼:图表(Chart) + 数据表(DataTable) + 最近活动(卡片列表)
10.4 主题系统落地
// 1) globals.css 定义品牌令牌(第 3 章)
// 2) ThemeProvider 包裹(第 2 章)
// 3) Header 里放 ModeToggle 切换明暗
// 4) 组件一律用语义类:bg-background / text-foreground / border-border
// 禁止硬编码 bg-white / text-black,否则暗色模式失效
最常见的翻车点
写死 bg-white、text-gray-900 会让暗色模式失灵。始终用语义令牌类(bg-card、text-muted-foreground),这样切 .dark 时颜色才会自动跟随。
10.5 组件复用与目录约定
src/
├─ components/
│ ├─ ui/ # shadcn 原语,尽量只按需微调
│ │ ├─ button.tsx
│ │ └─ ...
│ ├─ app-sidebar.tsx # 项目级复合组件
│ ├─ mode-toggle.tsx
│ └─ data-table.tsx
├─ features/ # 按业务域组织的组合
│ └─ users/
│ ├─ columns.tsx
│ └─ user-form.tsx
└─ lib/utils.ts # cn()
改 ui/ 要克制
ui/ 里的原语尽量保持接近官方,方便未来 re-run CLI 升级时对比。业务定制放到外层复合组件里,别把逻辑塞进原语文件。
10.6 a11y 无障碍清单
| 检查项 | 做法 |
|---|---|
| 图标按钮有名字 | 加 <span className="sr-only">关闭</span> 或 aria-label |
| 表单 Label 关联 | Label 的 htmlFor 对上 Input 的 id |
| Dialog 有标题 | 提供 DialogTitle(可 sr-only 隐藏) |
| 焦点可见 | 别移除 focus-visible:ring,保留焦点环 |
| 颜色对比度 | 正文 ≥ 4.5:1,用令牌而非低对比灰 |
| 键盘可达 | 所有交互能 Tab 到、Enter/Space 触发 |
| 破坏操作确认 | 删除等用 AlertDialog 二次确认 |
| 动效可关 | 尊重 prefers-reduced-motion |
10.7 与 v0 / AI 工作流
# v0.dev:描述需求 → 生成 shadcn+Tailwind 代码 → 粘进项目
# 因为产出就是 shadcn/ui,无需适配层
# shadcn MCP:让 AI 助手直接操作 registry
$ npx shadcn@latest mcp init # 配置 MCP server
# 之后可在 Claude/Cursor 里说"帮我加一个 data-table 并接入用户列表"
为什么 shadcn 特别适合 AI 时代
组件是纯文本源码、有清晰命名与结构,AI 极易读懂与生成;"复制进项目"的模式也天然契合 AI"写文件"的能力。v0 与 MCP 让这一切自动化。
10.8 部署要点
- 无特殊运行时:shadcn 组件就是你项目的 React 源码,按框架正常构建部署即可(Vercel / Netlify / Node / 静态导出)。
- Tailwind 产物:确保 Tailwind 扫描到
components/ui路径,否则生产构建可能 purge 掉用到的类。 - 字体与图标:lucide-react 按需 tree-shake;自定义字体走框架的字体优化。
- 暗色闪烁:SSR 场景用 next-themes 的脚本注入防止首屏主题闪烁(FOUC)。
10.9 常见坑位速查
| 症状 | 原因 / 修复 |
|---|---|
| @/ 找不到模块 | tsconfig paths 与打包器 alias 未同时配 |
| 样式没生效 | Tailwind content 未含组件路径 / 未 import 全局 CSS |
| 暗色不切换 | 写死颜色类,未用语义令牌 / 未加 .dark |
| Dialog 控制台警告 | 缺 DialogTitle / Description |
| 类名冲突不生效 | 未用 cn(),被 Tailwind 优先级坑 |
10.10 全教程总结
- shadcn/ui 的内核是"复制源码、代码归你所有"——Radix 管行为、Tailwind 管样式、CSS 变量管主题。
- 从 init 到 components.json、令牌系统、表单与验证、布局、覆盖层、DataTable、进阶组合与 Registry,你已掌握全链路。
- 工程化上:约定
ui/与业务组件分层、坚持语义令牌、照 a11y 清单验收、借 v0/MCP 把 AI 编织进工作流。 - 接下来动手把本章的 Dashboard 搭出来——那是把 10 章所学融会贯通的最佳练习。