shadcn-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

shadcn/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
    radix-ui
    skill for unstyled components
  • 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
undefined
bash
undefined

Initialize 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
undefined
npx shadcn@latest add button card dialog dropdown-menu form input table
undefined

Button 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-PatternWhy It's BadCorrect Approach
Modifying component files directlyLost on re-installWrap components or use variants
No DialogTitle/DialogDescriptionAccessibility issueAlways include for screen readers
Missing aria labelsNot accessibleAdd aria-label to interactive elements
Not using asChildExtra DOM nodesUse asChild to merge props
Hardcoding theme colorsCan't change themeUse CSS variables from globals.css
反模式弊端正确做法
直接修改组件源文件重新安装时修改会丢失封装组件或使用变体属性
缺失DialogTitle/DialogDescription存在无障碍问题始终为屏幕阅读器添加这些组件
缺失aria标签无障碍适配不合格为交互元素添加aria-label属性
不使用asChild属性产生多余DOM节点使用asChild合并属性
硬编码主题色无法切换主题使用globals.css中定义的CSS变量

Quick Troubleshooting

快速排障

IssueCauseSolution
Components not foundNot installedRun npx shadcn@latest add [component]
Styles not applyingGlobals.css not importedImport in layout/app
Dark mode not workingNo ThemeProviderWrap app in ThemeProvider
Type errorsMissing typesInstall @radix-ui/react-* peer deps
Dialog not closingNo controlled stateAdd open and onOpenChange props
Form validation not workingMissing zodResolverAdd 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

监控指标

MetricTarget
Component bundle size< 50KB per component
First Input Delay< 100ms
Accessibility score100%
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
  • 组件参考
  • 主题配置