shadcn-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseshadcn/ui Components
shadcn/ui 组件
Full Reference: See advanced.md for accessibility patterns, virtualized tables, form integration with react-hook-form, testing patterns, and dark mode setup.
完整参考:查看advanced.md了解无障碍模式、虚拟表格、与react-hook-form的表单集成、测试模式和暗黑模式配置。
When NOT to Use This Skill
何时不要使用本技能
- Radix UI primitives only - Use skill for unstyled components
radix-ui - Custom component library - Build from scratch with Radix + Tailwind
- Different UI framework - Material-UI, Chakra, Ant Design have own patterns
- No Tailwind project - shadcn/ui requires Tailwind CSS
- 仅使用Radix UI原语 - 请使用技能处理无样式组件相关问题
radix-ui - 自定义组件库 - 直接基于Radix + Tailwind从零构建
- 使用其他UI框架 - Material-UI、Chakra、Ant Design有各自的使用模式
- 项目未使用Tailwind - shadcn/ui依赖Tailwind CSS
Installation
安装
bash
undefinedbash
undefinedInitialize shadcn/ui in your project
在项目中初始化shadcn/ui
npx shadcn@latest init
npx shadcn@latest init
Add components
添加组件
npx shadcn@latest add button card dialog dropdown-menu form input table
undefinednpx shadcn@latest add button card dialog dropdown-menu form input table
undefinedButton Variants
Button 变体
tsx
import { Button } from '@/components/ui/button';
<Button variant="default">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
// With icon
<Button>
<PlusIcon className="mr-2 h-4 w-4" />
Add Item
</Button>
// Loading state
<Button disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Save
</Button>tsx
import { Button } from '@/components/ui/button';
<Button variant="default">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
// 带图标
<Button>
<PlusIcon className="mr-2 h-4 w-4" />
Add Item
</Button>
// 加载状态
<Button disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Save
</Button>Card Layout
Card 布局
tsx
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
<Card>
<CardHeader>
<CardTitle>User Profile</CardTitle>
</CardHeader>
<CardContent>
{/* Content */}
</CardContent>
</Card>tsx
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
<Card>
<CardHeader>
<CardTitle>User Profile</CardTitle>
</CardHeader>
<CardContent>
{/* 内容 */}
</CardContent>
</Card>Dialog (Modal)
Dialog (模态框)
tsx
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Create User</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create User</DialogTitle>
</DialogHeader>
<UserForm onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>tsx
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Create User</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create User</DialogTitle>
</DialogHeader>
<UserForm onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>Data Table
数据表格
tsx
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
</TableRow>
))}
</TableBody>
</Table>tsx
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
</TableRow>
))}
</TableBody>
</Table>Utils (cn function)
工具函数 (cn方法)
tsx
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage
<div className={cn("base-class", isActive && "active-class")} />tsx
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 使用示例
<div className={cn("base-class", isActive && "active-class")} />Anti-Patterns
反模式
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| Modifying component files directly | Lost on re-install | Wrap components or use variants |
| No DialogTitle/DialogDescription | Accessibility issue | Always include for screen readers |
| Missing aria labels | Not accessible | Add aria-label to interactive elements |
| Not using asChild | Extra DOM nodes | Use asChild to merge props |
| Hardcoding theme colors | Can't change theme | Use CSS variables from globals.css |
| 反模式 | 弊端 | 正确做法 |
|---|---|---|
| 直接修改组件源文件 | 重新安装时修改会丢失 | 封装组件或使用变体属性 |
| 缺失DialogTitle/DialogDescription | 存在无障碍问题 | 始终为屏幕阅读器添加这些组件 |
| 缺失aria标签 | 无障碍适配不合格 | 为交互元素添加aria-label属性 |
| 不使用asChild属性 | 产生多余DOM节点 | 使用asChild合并属性 |
| 硬编码主题色 | 无法切换主题 | 使用globals.css中定义的CSS变量 |
Quick Troubleshooting
快速排障
| Issue | Cause | Solution |
|---|---|---|
| Components not found | Not installed | Run npx shadcn@latest add [component] |
| Styles not applying | Globals.css not imported | Import in layout/app |
| Dark mode not working | No ThemeProvider | Wrap app in ThemeProvider |
| Type errors | Missing types | Install @radix-ui/react-* peer deps |
| Dialog not closing | No controlled state | Add open and onOpenChange props |
| Form validation not working | Missing zodResolver | Add resolver: zodResolver(schema) |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 找不到组件 | 未安装对应组件 | 执行npx shadcn@latest add [组件名] |
| 样式不生效 | 未引入Globals.css | 在layout/app文件中引入该样式 |
| 暗黑模式不生效 | 缺少ThemeProvider | 用ThemeProvider包裹整个应用 |
| 类型错误 | 缺失类型定义 | 安装@radix-ui/react-*相关 peer 依赖 |
| Dialog无法关闭 | 没有使用受控状态 | 添加open和onOpenChange属性 |
| 表单校验不生效 | 缺失zodResolver | 添加resolver: zodResolver(schema)配置 |
Theme Configuration
主题配置
css
/* index.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
/* ... more variables */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}css
/* index.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
/* ... 更多变量 */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}Monitoring Metrics
监控指标
| Metric | Target |
|---|---|
| Component bundle size | < 50KB per component |
| First Input Delay | < 100ms |
| Accessibility score | 100% |
| Form submission time | < 300ms |
| 指标 | 目标值 |
|---|---|
| 组件打包体积 | 单个组件<50KB |
| 首次输入延迟 | <100ms |
| 无障碍评分 | 100% |
| 表单提交耗时 | <300ms |
Checklist
检查清单
- Accessible labels on all form fields
- DialogTitle and DialogDescription present
- aria-describedby for error messages
- Loading states with aria-busy
- Lazy loading for heavy components
- Virtual scrolling for large lists
- Form validation with Zod
- Dark mode with next-themes
- Component tests with Testing Library
- Accessibility tests with jest-axe
- 所有表单字段都有无障碍标签
- 包含DialogTitle和DialogDescription
- 错误信息配置了aria-describedby
- 加载状态带有aria-busy属性
- 重型组件实现懒加载
- 长列表使用虚拟滚动
- 表单校验基于Zod实现
- 暗黑模式基于next-themes实现
- 使用Testing Library编写组件测试
- 使用jest-axe做无障碍测试
Reference Documentation
参考文档
- Component Reference
- Theming
- 组件参考
- 主题配置