shadcn-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

shadcn/ui - Component Library

shadcn/ui - 组件库


progressive_disclosure: entry_point: summary, when_to_use, quick_start estimated_tokens: entry: 85 full: 4800


progressive_disclosure: entry_point: summary, when_to_use, quick_start estimated_tokens: entry: 85 full: 4800

Summary

概述

shadcn/ui is a collection of re-usable React components built with Radix UI primitives and styled with Tailwind CSS. Unlike traditional component libraries, shadcn/ui components are copied directly into your project, giving you full ownership and customization control. Components are accessible, customizable, and open source.
Core Philosophy: Copy-paste components, not npm packages. You own the code.
shadcn/ui是一组基于Radix UI原语构建、用Tailwind CSS样式化的可复用React组件。与传统组件库不同,shadcn/ui组件可直接复制到你的项目中,让你完全拥有代码所有权和自定义控制权。组件具备无障碍访问能力、可自定义且开源。
核心理念:复制粘贴组件,而非安装npm包。你拥有代码所有权。

When to Use

适用场景

Use shadcn/ui when:
  • Building React applications with Tailwind CSS
  • Need accessible, production-ready UI components
  • Want full control over component code and styling
  • Prefer composition over configuration
  • Building with Next.js, Vite, Remix, or Astro
  • Need dark mode support out of the box
  • Want TypeScript-first components
Don't use when:
  • Not using Tailwind CSS (core styling dependency)
  • Need legacy browser support (uses modern CSS features)
  • Prefer packaged npm libraries over code ownership
  • Building non-React frameworks (Vue, Svelte, Angular)
适合使用shadcn/ui的场景
  • 构建基于Tailwind CSS的React应用
  • 需要具备无障碍访问能力、可用于生产环境的UI组件
  • 希望完全控制组件代码和样式
  • 偏好组合而非配置的开发方式
  • 使用Next.js、Vite、Remix或Astro框架
  • 需要开箱即用的深色模式支持
  • 优先选择TypeScript组件
不适合使用的场景
  • 未使用Tailwind CSS(核心样式依赖)
  • 需要兼容旧版浏览器(使用了现代CSS特性)
  • 偏好封装好的npm库而非代码所有权
  • 构建非React框架的应用(Vue、Svelte、Angular)

Quick Start

快速开始

Installation

安装

bash
undefined
bash
undefined

Initialize shadcn/ui in your project

在你的项目中初始化shadcn/ui

npx shadcn-ui@latest init
npx shadcn-ui@latest init

Follow interactive prompts:

跟随交互式提示操作:

- TypeScript? (yes/no)

- 是否使用TypeScript?(是/否)

- Style: Default/New York

- 样式风格:默认/纽约风

- Base color: Slate/Gray/Zinc/Neutral/Stone

- 基础颜色:Slate/Gray/Zinc/Neutral/Stone

- CSS variables: (yes/no)

- 是否使用CSS变量?(是/否)

- React Server Components: (yes/no)

- 是否支持React Server Components?(是/否)

- components.json location

- components.json文件位置

- Tailwind config location

- Tailwind配置文件位置

- CSS file location

- CSS文件位置

- Import alias (@/components)

- 导入别名(@/components)

undefined
undefined

Add Your First Component

添加第一个组件

bash
undefined
bash
undefined

Add individual components

添加单个组件

npx shadcn-ui@latest add button npx shadcn-ui@latest add card npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add button npx shadcn-ui@latest add card npx shadcn-ui@latest add dialog

Add multiple components at once

同时添加多个组件

npx shadcn-ui@latest add button card dialog form input
undefined
npx shadcn-ui@latest add button card dialog form input
undefined

Basic Usage

基础用法

tsx
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"

export default function Example() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Welcome</CardTitle>
      </CardHeader>
      <CardContent>
        <Button>Click me</Button>
      </CardContent>
    </Card>
  )
}

tsx
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"

export default function Example() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Welcome</CardTitle>
      </CardHeader>
      <CardContent>
        <Button>Click me</Button>
      </CardContent>
    </Card>
  )
}

Architecture

架构

Copy-Paste Philosophy

复制粘贴理念

Key Difference from Traditional Libraries:
  • Traditional:
    npm install component-library
    → locked to package versions
  • shadcn/ui: Components copied to
    components/ui/
    → you own the code
Benefits:
  • Full customization control
  • No breaking changes from package updates
  • Easy to modify for specific needs
  • Transparent implementation
  • Tree-shakeable by default
与传统组件库的核心区别
  • 传统方式
    npm install component-library
    → 受限于包版本
  • shadcn/ui:组件复制到
    components/ui/
    目录 → 你拥有代码所有权
优势
  • 完全的自定义控制权
  • 不会因包更新导致破坏性变更
  • 可轻松修改以满足特定需求
  • 实现透明化
  • 默认支持摇树优化

Component Structure

组件结构

src/
├── components/
│   └── ui/
│       ├── button.tsx      # Component implementation
│       ├── card.tsx        # Owns its code
│       ├── dialog.tsx      # Modifiable
│       └── ...
├── lib/
│   └── utils.ts            # cn() helper for class merging
└── app/
    └── globals.css         # Tailwind directives + CSS variables
src/
├── components/
│   └── ui/
│       ├── button.tsx      # 组件实现代码
│       ├── card.tsx        # 你拥有代码所有权
│       ├── dialog.tsx      # 可修改
│       └── ...
├── lib/
│   └── utils.ts            # 用于类名合并的cn()工具函数
└── app/
    └── globals.css         # Tailwind指令 + CSS变量

Technology Stack

技术栈

Core Dependencies:
  • Radix UI: Accessible component primitives (headless UI)
  • Tailwind CSS: Utility-first styling
  • TypeScript: Type safety
  • class-variance-authority (CVA): Variant management
  • clsx: Class name concatenation
  • tailwind-merge: Conflict-free class merging
Radix UI Integration:
tsx
// shadcn/ui components wrap Radix primitives
import * as DialogPrimitive from "@radix-ui/react-dialog"

// Add styling and variants
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogContent = React.forwardRef<...>(
  ({ className, children, ...props }, ref) => (
    <DialogPrimitive.Content
      ref={ref}
      className={cn("fixed ...", className)}
      {...props}
    />
  )
)
核心依赖
  • Radix UI:具备无障碍访问能力的组件原语(无头UI)
  • Tailwind CSS:实用优先的样式框架
  • TypeScript:类型安全
  • class-variance-authority (CVA):变体管理
  • clsx:类名拼接
  • tailwind-merge:无冲突的类名合并
Radix UI集成示例
tsx
// shadcn/ui组件封装了Radix原语
import * as DialogPrimitive from "@radix-ui/react-dialog"

// 添加样式和变体
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogContent = React.forwardRef<...>(
  ({ className, children, ...props }, ref) => (
    <DialogPrimitive.Content
      ref={ref}
      className={cn("fixed ...", className)}
      {...props}
    />
  )
)

Configuration

配置

components.json

components.json

json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}
Key Options:
  • style
    : "default" or "new-york" (design variants)
  • rsc
    : React Server Components support
  • cssVariables
    : Use CSS variables for theming
  • prefix
    : Tailwind class prefix (optional)
json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}
关键配置项
  • style
    :"default"或"new-york"(设计变体)
  • rsc
    :是否支持React Server Components
  • cssVariables
    :是否使用CSS变量进行主题配置
  • prefix
    :Tailwind类名前缀(可选)

Tailwind Configuration

Tailwind配置

ts
// tailwind.config.ts
import type { Config } from "tailwindcss"

const config = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  prefix: "",
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: "0" },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config

export default config
ts
// tailwind.config.ts
import type { Config } from "tailwindcss"

const config = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  prefix: "",
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: "0" },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config

export default config

CSS Variables (globals.css)

CSS变量(globals.css)

css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}
css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Component Catalog

组件目录

Button

按钮(Button)

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

// Variants
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>

// States
<Button disabled>Disabled</Button>
<Button asChild>
  <Link href="/about">As Link</Link>
</Button>
Implementation Pattern (CVA):
tsx
import { cva, type VariantProps } from "class-variance-authority"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)
tsx
import { Button } from "@/components/ui/button"

// 变体
<Button variant="default">默认按钮</Button>
<Button variant="destructive">危险按钮</Button>
<Button variant="outline">轮廓按钮</Button>
<Button variant="secondary">次要按钮</Button>
<Button variant="ghost">幽灵按钮</Button>
<Button variant="link">链接按钮</Button>

// 尺寸
<Button size="default">默认尺寸</Button>
<Button size="sm">小尺寸</Button>
<Button size="lg">大尺寸</Button>
<Button size="icon"><Icon /></Button>

// 状态
<Button disabled>禁用状态</Button>
<Button asChild>
  <Link href="/about">作为链接</Link>
</Button>
实现模式(CVA)
tsx
import { cva, type VariantProps } from "class-variance-authority"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

Card

卡片(Card)

tsx
import {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardDescription,
  CardContent,
} from "@/components/ui/card"

<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Card description goes here</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Card content</p>
  </CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>
tsx
import {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardDescription,
  CardContent,
} from "@/components/ui/card"

<Card>
  <CardHeader>
    <CardTitle>卡片标题</CardTitle>
    <CardDescription>卡片描述信息</CardDescription>
  </CardHeader>
  <CardContent>
    <p>卡片内容</p>
  </CardContent>
  <CardFooter>
    <Button>操作按钮</Button>
  </CardFooter>
</Card>

Dialog (Modal)

对话框(Dialog/Modal)

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

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you sure?</DialogTitle>
      <DialogDescription>
        This action cannot be undone.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button>Confirm</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
tsx
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
  DialogFooter,
} from "@/components/ui/dialog"

<Dialog>
  <DialogTrigger asChild>
    <Button>打开对话框</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>你确定吗?</DialogTitle>
      <DialogDescription>
        此操作无法撤销。
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">取消</Button>
      <Button>确认</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Form (with react-hook-form + zod)

表单(Form,基于react-hook-form + zod)

tsx
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

const formSchema = z.object({
  username: z.string().min(2, {
    message: "Username must be at least 2 characters.",
  }),
  email: z.string().email({
    message: "Please enter a valid email address.",
  }),
})

function ProfileForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: "",
      email: "",
    },
  })

  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <Input placeholder="shadcn" {...field} />
              </FormControl>
              <FormDescription>
                This is your public display name.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="user@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  )
}
tsx
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

const formSchema = z.object({
  username: z.string().min(2, {
    message: "用户名至少需要2个字符。",
  }),
  email: z.string().email({
    message: "请输入有效的邮箱地址。",
  }),
})

function ProfileForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: "",
      email: "",
    },
  })

  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>用户名</FormLabel>
              <FormControl>
                <Input placeholder="shadcn" {...field} />
              </FormControl>
              <FormDescription>
                这是你的公开显示名称。
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>邮箱</FormLabel>
              <FormControl>
                <Input type="email" placeholder="user@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">提交</Button>
      </form>
    </Form>
  )
}

Table

表格(Table)

tsx
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

<Table>
  <TableCaption>A list of your recent invoices.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead>Method</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell>Credit Card</TableCell>
      <TableCell className="text-right">$250.00</TableCell>
    </TableRow>
  </TableBody>
</Table>
tsx
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

<Table>
  <TableCaption>你最近的发票列表。</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>发票编号</TableHead>
      <TableHead>状态</TableHead>
      <TableHead>支付方式</TableHead>
      <TableHead className="text-right">金额</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV001</TableCell>
      <TableCell>已支付</TableCell>
      <TableCell>信用卡</TableCell>
      <TableCell className="text-right">$250.00</TableCell>
    </TableRow>
  </TableBody>
</Table>

Additional Components

更多组件

Available via CLI:
  • accordion
    - Collapsible content sections
  • alert
    - Contextual feedback messages
  • alert-dialog
    - Interrupting modal dialogs
  • avatar
    - User profile images
  • badge
    - Status indicators
  • calendar
    - Date picker
  • checkbox
    - Binary input
  • command
    - Command palette (⌘K menu)
  • context-menu
    - Right-click menus
  • dropdown-menu
    - Dropdown menus
  • hover-card
    - Hover tooltips
  • input
    - Text input
  • label
    - Form labels
  • menubar
    - Application menu bar
  • navigation-menu
    - Site navigation
  • popover
    - Floating panels
  • progress
    - Progress indicators
  • radio-group
    - Radio button groups
  • scroll-area
    - Custom scrollbars
  • select
    - Dropdown selects
  • separator
    - Visual dividers
  • sheet
    - Side panels
  • skeleton
    - Loading placeholders
  • slider
    - Range input
  • switch
    - Toggle switch
  • tabs
    - Tab navigation
  • textarea
    - Multi-line input
  • toast
    - Notification toasts
  • toggle
    - Toggle button
  • tooltip
    - Hover tooltips
可通过CLI添加的组件
  • accordion
    - 可折叠内容区块
  • alert
    - 上下文反馈消息
  • alert-dialog
    - 中断式模态对话框
  • avatar
    - 用户头像
  • badge
    - 状态指示器
  • calendar
    - 日期选择器
  • checkbox
    - 复选框
  • command
    - 命令面板(⌘K菜单)
  • context-menu
    - 右键菜单
  • dropdown-menu
    - 下拉菜单
  • hover-card
    - 悬停提示卡片
  • input
    - 文本输入框
  • label
    - 表单标签
  • menubar
    - 应用菜单栏
  • navigation-menu
    - 站点导航菜单
  • popover
    - 浮动面板
  • progress
    - 进度指示器
  • radio-group
    - 单选按钮组
  • scroll-area
    - 自定义滚动区域
  • select
    - 下拉选择器
  • separator
    - 视觉分隔线
  • sheet
    - 侧边面板
  • skeleton
    - 加载占位符
  • slider
    - 范围滑块
  • switch
    - 开关按钮
  • tabs
    - 标签页导航
  • textarea
    - 多行文本输入框
  • toast
    - 通知提示框
  • toggle
    - 切换按钮
  • tooltip
    - 悬停提示

Theming

主题定制

Color Customization

颜色自定义

Change base color scheme:
bash
undefined
更改基础配色方案
bash
undefined

Regenerate components with new base color

使用新的基础颜色重新生成组件

npx shadcn-ui@latest init
npx shadcn-ui@latest init

Choose new base: Slate, Gray, Zinc, Neutral, Stone

选择新的基础颜色:Slate、Gray、Zinc、Neutral、Stone


**Manual color override** (globals.css):
```css
:root {
  --primary: 210 100% 50%;  /* HSL: Blue */
  --primary-foreground: 0 0% 100%;
}

.dark {
  --primary: 210 100% 60%;  /* Lighter blue for dark mode */
}

**手动覆盖颜色(globals.css)**:
```css
:root {
  --primary: 210 100% 50%;  /* HSL:蓝色 */
  --primary-foreground: 0 0% 100%;
}

.dark {
  --primary: 210 100% 60%;  /* 深色模式下的浅蓝 */
}

Custom Variants

自定义变体

tsx
// Extend button variants
const buttonVariants = cva(
  "...",
  {
    variants: {
      variant: {
        // ...existing variants
        gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
      },
    },
  }
)

// Usage
<Button variant="gradient">Gradient Button</Button>
tsx
// 扩展按钮变体
const buttonVariants = cva(
  "...",
  {
    variants: {
      variant: {
        // ...现有变体
        gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
      },
    },
  }
)

// 使用方式
<Button variant="gradient">渐变按钮</Button>

Theme Switching

主题切换

tsx
// Using next-themes
import { ThemeProvider } from "next-themes"

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

// Theme toggle component
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { setTheme, theme } = useTheme()

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  )
}
tsx
// 使用next-themes库
import { ThemeProvider } from "next-themes"

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

// 主题切换组件
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { setTheme, theme } = useTheme()

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">切换主题</span>
    </Button>
  )
}

Dark Mode

深色模式

Setup with Next.js

Next.js配置

bash
npm install next-themes
tsx
// app/providers.tsx
"use client"

import { ThemeProvider } from "next-themes"

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </ThemeProvider>
  )
}

// app/layout.tsx
import { Providers } from "./providers"

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
bash
npm install next-themes
tsx
// app/providers.tsx
"use client"

import { ThemeProvider } from "next-themes"

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </ThemeProvider>
  )
}

// app/layout.tsx
import { Providers } from "./providers"

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

Dark Mode Utilities

深色模式工具

tsx
// Force dark mode for specific section
<div className="dark">
  <Card>Always dark, regardless of theme</Card>
</div>

// Conditional styling
<div className="bg-white dark:bg-slate-950">
  <p className="text-slate-900 dark:text-slate-50">
    Adapts to theme
  </p>
</div>
tsx
// 强制特定区域使用深色模式
<div className="dark">
  <Card>始终为深色,不受主题影响</Card>
</div>

// 条件样式
<div className="bg-white dark:bg-slate-950">
  <p className="text-slate-900 dark:text-slate-50">
    适配主题的文本
  </p>
</div>

Next.js Integration

Next.js集成

App Router Setup

App Router配置

bash
undefined
bash
undefined

Create Next.js app with TypeScript and Tailwind

创建带有TypeScript和Tailwind的Next.js应用

npx create-next-app@latest my-app --typescript --tailwind --app
npx create-next-app@latest my-app --typescript --tailwind --app

Initialize shadcn/ui

初始化shadcn/ui

cd my-app npx shadcn-ui@latest init
cd my-app npx shadcn-ui@latest init

Add components

添加组件

npx shadcn-ui@latest add button card form
undefined
npx shadcn-ui@latest add button card form
undefined

Server Components

服务端组件

tsx
// app/page.tsx (Server Component by default)
import { Button } from "@/components/ui/button"

export default function HomePage() {
  return (
    <main>
      <h1>Welcome</h1>
      {/* Static components work in Server Components */}
      <Button asChild>
        <a href="/about">Learn More</a>
      </Button>
    </main>
  )
}
tsx
// app/page.tsx(默认是服务端组件)
import { Button } from "@/components/ui/button"

export default function HomePage() {
  return (
    <main>
      <h1>欢迎</h1>
      {/* 静态组件可在服务端组件中使用 */}
      <Button asChild>
        <a href="/about">了解更多</a>
      </Button>
    </main>
  )
}

Client Components

客户端组件

tsx
// app/interactive.tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"

export function InteractiveSection() {
  const [open, setOpen] = useState(false)

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <p>Client-side interactivity</p>
      </DialogContent>
    </Dialog>
  )
}
tsx
// app/interactive.tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"

export function InteractiveSection() {
  const [open, setOpen] = useState(false)

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button>打开对话框</Button>
      </DialogTrigger>
      <DialogContent>
        <p>客户端交互内容</p>
      </DialogContent>
    </Dialog>
  )
}

Route Handlers

路由处理器

tsx
// app/api/submit/route.ts
import { NextResponse } from "next/server"
import { z } from "zod"

const formSchema = z.object({
  email: z.string().email(),
  message: z.string().min(10),
})

export async function POST(request: Request) {
  try {
    const body = await request.json()
    const validatedData = formSchema.parse(body)

    // Process form data
    return NextResponse.json({ success: true })
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json({ errors: error.errors }, { status: 400 })
    }
    return NextResponse.json({ error: "Internal error" }, { status: 500 })
  }
}
tsx
// app/api/submit/route.ts
import { NextResponse } from "next/server"
import { z } from "zod"

const formSchema = z.object({
  email: z.string().email(),
  message: z.string().min(10),
})

export async function POST(request: Request) {
  try {
    const body = await request.json()
    const validatedData = formSchema.parse(body)

    // 处理表单数据
    return NextResponse.json({ success: true })
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json({ errors: error.errors }, { status: 400 })
    }
    return NextResponse.json({ error: "内部错误" }, { status: 500 })
  }
}

Accessibility

无障碍访问

ARIA Support

ARIA支持

All shadcn/ui components include proper ARIA attributes via Radix UI:
tsx
// Dialog automatically includes:
// - role="dialog"
// - aria-describedby
// - aria-labelledby
// - Focus trap
// - Escape key handler
<Dialog>
  <DialogContent>
    {/* Automatically accessible */}
  </DialogContent>
</Dialog>

// Button includes:
// - role="button"
// - tabindex="0"
// - Keyboard activation (Space/Enter)
<Button>Accessible by default</Button>
所有shadcn/ui组件通过Radix UI内置了完善的ARIA属性:
tsx
// 对话框自动包含:
// - role="dialog"
// - aria-describedby
// - aria-labelledby
// - 焦点陷阱
// - ESC键关闭处理
<Dialog>
  <DialogContent>
    {/* 自动具备无障碍访问能力 */}
  </DialogContent>
</Dialog>

// 按钮包含:
// - role="button"
// - tabindex="0"
// - 键盘激活支持(空格/回车)
<Button>默认具备无障碍访问能力</Button>

Keyboard Navigation

键盘导航

Built-in keyboard support:
  • Tab
    /
    Shift+Tab
    - Navigate between interactive elements
  • Enter
    /
    Space
    - Activate buttons
  • Escape
    - Close dialogs, dropdowns, popovers
  • Arrow keys
    - Navigate menus, select options, radio groups
  • Home
    /
    End
    - Jump to first/last in lists
Example: Command Palette:
tsx
import {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
} from "@/components/ui/command"

// ⌘K to open
<CommandDialog open={open} onOpenChange={setOpen}>
  <CommandInput placeholder="Type a command..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem>Calendar</CommandItem>
      <CommandItem>Search Emoji</CommandItem>
      <CommandItem>Calculator</CommandItem>
    </CommandGroup>
  </CommandList>
</CommandDialog>
内置键盘支持
  • Tab
    /
    Shift+Tab
    - 在交互式元素间导航
  • Enter
    /
    Space
    - 激活按钮
  • Escape
    - 关闭对话框、下拉菜单、浮动面板
  • 方向键 - 导航菜单、选择选项、单选按钮组
  • Home
    /
    End
    - 跳转到列表的第一个/最后一个元素
示例:命令面板
tsx
import {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
} from "@/components/ui/command"

// 按⌘K打开
<CommandDialog open={open} onOpenChange={setOpen}>
  <CommandInput placeholder="输入命令..." />
  <CommandList>
    <CommandEmpty>未找到结果。</CommandEmpty>
    <CommandGroup heading="推荐">
      <CommandItem>日历</CommandItem>
      <CommandItem>搜索表情</CommandItem>
      <CommandItem>计算器</CommandItem>
    </CommandGroup>
  </CommandList>
</CommandDialog>

Screen Reader Support

屏幕阅读器支持

tsx
// Visually hidden but accessible to screen readers
<span className="sr-only">Close dialog</span>

// Skip navigation links
<a href="#main-content" className="sr-only focus:not-sr-only">
  Skip to main content
</a>

// Descriptive labels
<FormLabel htmlFor="email">Email address</FormLabel>
<Input
  id="email"
  type="email"
  aria-describedby="email-description"
  aria-invalid={!!errors.email}
/>
<FormDescription id="email-description">
  We'll never share your email.
</FormDescription>
tsx
// 视觉隐藏但屏幕阅读器可访问
<span className="sr-only">关闭对话框</span>

// 跳过导航链接
<a href="#main-content" className="sr-only focus:not-sr-only">
  跳转到主要内容
</a>

// 描述性标签
<FormLabel htmlFor="email">邮箱地址</FormLabel>
<Input
  id="email"
  type="email"
  aria-describedby="email-description"
  aria-invalid={!!errors.email}
/>
<FormDescription id="email-description">
  我们绝不会分享你的邮箱。
</FormDescription>

Focus Management

焦点管理

tsx
// Focus trap in Dialog (automatic)
<Dialog>
  <DialogContent>
    {/* Focus stays within dialog until closed */}
  </DialogContent>
</Dialog>

// Custom focus management
import { useRef, useEffect } from "react"

function CustomComponent() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return <Input ref={inputRef} />
}
tsx
// 对话框自动实现焦点陷阱
<Dialog>
  <DialogContent>
    {/* 焦点会一直停留在对话框内直到关闭 */}
  </DialogContent>
</Dialog>

// 自定义焦点管理
import { useRef, useEffect } from "react"

function CustomComponent() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return <Input ref={inputRef} />
}

Composition Patterns

组合模式

Compound Components

复合组件

tsx
// Card composition
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Description</CardDescription>
  </CardHeader>
  <CardContent>Content</CardContent>
  <CardFooter>Footer</CardFooter>
</Card>

// Form composition
<Form {...form}>
  <FormField
    control={form.control}
    name="field"
    render={({ field }) => (
      <FormItem>
        <FormLabel>Label</FormLabel>
        <FormControl>
          <Input {...field} />
        </FormControl>
        <FormDescription>Help text</FormDescription>
        <FormMessage />
      </FormItem>
    )}
  />
</Form>
tsx
// 卡片组合
<Card>
  <CardHeader>
    <CardTitle>标题</CardTitle>
    <CardDescription>描述</CardDescription>
  </CardHeader>
  <CardContent>内容</CardContent>
  <CardFooter>页脚</CardFooter>
</Card>

// 表单组合
<Form {...form}>
  <FormField
    control={form.control}
    name="field"
    render={({ field }) => (
      <FormItem>
        <FormLabel>标签</FormLabel>
        <FormControl>
          <Input {...field} />
        </FormControl>
        <FormDescription>帮助文本</FormDescription>
        <FormMessage />
      </FormItem>
    )}
  />
</Form>

Polymorphic Components (asChild)

多态组件(asChild)

tsx
// Render Button as Link
import { Button } from "@/components/ui/button"
import Link from "next/link"

<Button asChild>
  <Link href="/dashboard">Go to Dashboard</Link>
</Button>

// Render as custom component
<Button asChild>
  <motion.button
    whileHover={{ scale: 1.05 }}
    whileTap={{ scale: 0.95 }}
  >
    Animated Button
  </motion.button>
</Button>
How it works (Radix Slot):
tsx
import { Slot } from "@radix-ui/react-slot"

interface ButtonProps {
  asChild?: boolean
}

const Button = ({ asChild, ...props }: ButtonProps) => {
  const Comp = asChild ? Slot : "button"
  return <Comp {...props} />
}
tsx
// 将按钮渲染为链接
import { Button } from "@/components/ui/button"
import Link from "next/link"

<Button asChild>
  <Link href="/dashboard">前往控制台</Link>
</Button>

// 渲染为自定义组件
<Button asChild>
  <motion.button
    whileHover={{ scale: 1.05 }}
    whileTap={{ scale: 0.95 }}
  >
    带动画的按钮
  </motion.button>
</Button>
实现原理(Radix Slot)
tsx
import { Slot } from "@radix-ui/react-slot"

interface ButtonProps {
  asChild?: boolean
}

const Button = ({ asChild, ...props }: ButtonProps) => {
  const Comp = asChild ? Slot : "button"
  return <Comp {...props} />
}

Custom Compositions

自定义组合

tsx
// Create custom card variant
export function PricingCard({
  title,
  price,
  features,
  highlighted
}: PricingCardProps) {
  return (
    <Card className={cn(highlighted && "border-primary")}>
      <CardHeader>
        <CardTitle>{title}</CardTitle>
        <CardDescription className="text-3xl font-bold">
          ${price}/mo
        </CardDescription>
      </CardHeader>
      <CardContent>
        <ul className="space-y-2">
          {features.map((feature) => (
            <li key={feature} className="flex items-center">
              <Check className="mr-2 h-4 w-4 text-primary" />
              {feature}
            </li>
          ))}
        </ul>
      </CardContent>
      <CardFooter>
        <Button className="w-full" variant={highlighted ? "default" : "outline"}>
          Get Started
        </Button>
      </CardFooter>
    </Card>
  )
}
tsx
// 创建自定义卡片变体
export function PricingCard({
  title,
  price,
  features,
  highlighted
}: PricingCardProps) {
  return (
    <Card className={cn(highlighted && "border-primary")}>
      <CardHeader>
        <CardTitle>{title}</CardTitle>
        <CardDescription className="text-3xl font-bold">
          ${price}/月
        </CardDescription>
      </CardHeader>
      <CardContent>
        <ul className="space-y-2">
          {features.map((feature) => (
            <li key={feature} className="flex items-center">
              <Check className="mr-2 h-4 w-4 text-primary" />
              {feature}
            </li>
          ))}
        </ul>
      </CardContent>
      <CardFooter>
        <Button className="w-full" variant={highlighted ? "default" : "outline"}>
          开始使用
        </Button>
      </CardFooter>
    </Card>
  )
}

CLI Commands

CLI命令

Initialize

初始化

bash
undefined
bash
undefined

Interactive init

交互式初始化

npx shadcn-ui@latest init
npx shadcn-ui@latest init

Non-interactive with defaults

使用默认值非交互式初始化

npx shadcn-ui@latest init -y
npx shadcn-ui@latest init -y

Specify options

指定配置选项

npx shadcn-ui@latest init --typescript --tailwind
undefined
npx shadcn-ui@latest init --typescript --tailwind
undefined

Add Components

添加组件

bash
undefined
bash
undefined

Single component

添加单个组件

npx shadcn-ui@latest add button
npx shadcn-ui@latest add button

Multiple components

添加多个组件

npx shadcn-ui@latest add button card dialog form
npx shadcn-ui@latest add button card dialog form

All components (not recommended - adds everything)

添加所有组件(不推荐,会添加全部内容)

npx shadcn-ui@latest add --all
npx shadcn-ui@latest add --all

Specific version

添加指定版本的组件

npx shadcn-ui@latest add button@1.0.0
npx shadcn-ui@latest add button@1.0.0

Overwrite existing

覆盖现有组件

npx shadcn-ui@latest add button --overwrite
npx shadcn-ui@latest add button --overwrite

Different path

指定路径添加

npx shadcn-ui@latest add button --path src/components/ui
undefined
npx shadcn-ui@latest add button --path src/components/ui
undefined

Diff Components

对比组件差异

bash
undefined
bash
undefined

Check for component updates

检查组件更新

npx shadcn-ui@latest diff
npx shadcn-ui@latest diff

Diff specific component

对比指定组件的差异

npx shadcn-ui@latest diff button
npx shadcn-ui@latest diff button

Show what would change

仅显示即将变更的内容

npx shadcn-ui@latest diff --check
undefined
npx shadcn-ui@latest diff --check
undefined

Update Components

更新组件

bash
undefined
bash
undefined

Update all components

更新所有组件

npx shadcn-ui@latest update
npx shadcn-ui@latest update

Update specific components

更新指定组件

npx shadcn-ui@latest update button card
npx shadcn-ui@latest update button card

Preview changes before applying

预览变更内容而不实际应用

npx shadcn-ui@latest update --dry-run
undefined
npx shadcn-ui@latest update --dry-run
undefined

Advanced Patterns

高级模式

Custom Hooks

自定义Hook

tsx
// useToast hook (built-in with toast component)
import { useToast } from "@/components/ui/use-toast"

function MyComponent() {
  const { toast } = useToast()

  return (
    <Button
      onClick={() => {
        toast({
          title: "Scheduled: Catch up",
          description: "Friday, February 10, 2023 at 5:57 PM",
        })
      }}
    >
      Show Toast
    </Button>
  )
}

// Custom form hook
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"

function useFormWithToast<T extends z.ZodType>(schema: T) {
  const { toast } = useToast()
  const form = useForm({
    resolver: zodResolver(schema),
  })

  const handleSubmit = form.handleSubmit(async (data) => {
    try {
      // Submit logic
      toast({ title: "Success!" })
    } catch (error) {
      toast({ title: "Error", variant: "destructive" })
    }
  })

  return { form, handleSubmit }
}
tsx
// useToast Hook(与toast组件内置)
import { useToast } from "@/components/ui/use-toast"

function MyComponent() {
  const { toast } = useToast()

  return (
    <Button
      onClick={() => {
        toast({
          title: "已安排:跟进事项",
          description: "2023年2月10日周五下午5:57",
        })
      }}
    >
      显示提示
    </Button>
  )
}

// 自定义表单Hook
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"

function useFormWithToast<T extends z.ZodType>(schema: T) {
  const { toast } = useToast()
  const form = useForm({
    resolver: zodResolver(schema),
  })

  const handleSubmit = form.handleSubmit(async (data) => {
    try {
      // 提交逻辑
      toast({ title: "成功!" })
    } catch (error) {
      toast({ title: "错误", variant: "destructive" })
    }
  })

  return { form, handleSubmit }
}

Responsive Design

响应式设计

tsx
// Mobile-first responsive components
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <Card>Mobile: 1 col, Tablet: 2 col, Desktop: 3 col</Card>
</div>

// Responsive dialog (sheet on mobile, dialog on desktop)
import { useMediaQuery } from "@/hooks/use-media-query"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { Sheet, SheetContent } from "@/components/ui/sheet"

function ResponsiveModal({ children, ...props }) {
  const isDesktop = useMediaQuery("(min-width: 768px)")

  if (isDesktop) {
    return (
      <Dialog {...props}>
        <DialogContent>{children}</DialogContent>
      </Dialog>
    )
  }

  return (
    <Sheet {...props}>
      <SheetContent>{children}</SheetContent>
    </Sheet>
  )
}
tsx
// 移动端优先的响应式组件
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <Card>移动端:1列,平板:2列,桌面端:3列</Card>
</div>

// 响应式对话框(移动端为侧边面板,桌面端为对话框)
import { useMediaQuery } from "@/hooks/use-media-query"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { Sheet, SheetContent } from "@/components/ui/sheet"

function ResponsiveModal({ children, ...props }) {
  const isDesktop = useMediaQuery("(min-width: 768px)")

  if (isDesktop) {
    return (
      <Dialog {...props}>
        <DialogContent>{children}</DialogContent>
      </Dialog>
    )
  }

  return (
    <Sheet {...props}>
      <SheetContent>{children}</SheetContent>
    </Sheet>
  )
}

Animation Variants

动画变体

tsx
// Using Framer Motion with shadcn/ui
import { motion } from "framer-motion"
import { Card } from "@/components/ui/card"

const MotionCard = motion(Card)

<MotionCard
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.3 }}
>
  Animated Card
</MotionCard>

// Staggered list animation
const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1
    }
  }
}

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 }
}

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map((item) => (
    <motion.li key={item.id} variants={item}>
      <Card>{item.content}</Card>
    </motion.li>
  ))}
</motion.ul>
tsx
// 将shadcn/ui与Framer Motion结合使用
import { motion } from "framer-motion"
import { Card } from "@/components/ui/card"

const MotionCard = motion(Card)

<MotionCard
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.3 }}
>
  带动画的卡片
</MotionCard>

// 列表 stagger 动画
const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1
    }
  }
}

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 }
}

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map((item) => (
    <motion.li key={item.id} variants={item}>
      <Card>{item.content}</Card>
    </motion.li>
  ))}
</motion.ul>

Best Practices

最佳实践

Code Organization:
  • Keep shadcn/ui components in
    components/ui/
    (don't mix with app components)
  • Create custom compositions in
    components/
    (outside ui/)
  • Use
    lib/utils.ts
    for shared utilities
Customization:
  • Modify components directly in your project (you own the code)
  • Use CSS variables for theme-wide changes
  • Extend variants with CVA for new styles
  • Don't edit
    components.json
    manually (use CLI)
Performance:
  • Tree-shaking automatic (only imports what you use)
  • Use
    asChild
    to avoid unnecessary wrapper elements
  • Lazy load heavy components (Calendar, Command)
  • Prefer Server Components when possible (Next.js)
Accessibility:
  • Don't remove ARIA attributes from components
  • Test keyboard navigation for custom compositions
  • Maintain focus management in dialogs/modals
  • Use semantic HTML with
    asChild
    when applicable
TypeScript:
  • Leverage exported types (ButtonProps, CardProps, etc.)
  • Use VariantProps for variant type safety
  • Add strict null checks for form validation
代码组织
  • 将shadcn/ui组件放在
    components/ui/
    目录下(不要与业务组件混合)
  • components/
    目录下创建自定义组合组件(ui目录外)
  • 使用
    lib/utils.ts
    存放共享工具函数
自定义
  • 直接在项目中修改组件代码(你拥有所有权)
  • 使用CSS变量进行全局主题变更
  • 通过CVA扩展变体来添加新样式
  • 不要手动编辑
    components.json
    (使用CLI操作)
性能
  • 自动支持摇树优化(仅导入你使用的内容)
  • 使用
    asChild
    避免不必要的包装元素
  • 懒加载重型组件(日历、命令面板)
  • 尽可能使用服务端组件(Next.js)
无障碍访问
  • 不要移除组件的ARIA属性
  • 测试自定义组合组件的键盘导航
  • 保持对话框/模态框的焦点管理
  • 使用
    asChild
    时确保语义化HTML
TypeScript
  • 利用导出的类型(ButtonProps、CardProps等)
  • 使用VariantProps确保变体类型安全
  • 为表单验证添加严格的空值检查

Troubleshooting

故障排除

Import errors:
bash
undefined
导入错误
bash
undefined

Check path aliases in tsconfig.json

检查tsconfig.json中的路径别名

{ "compilerOptions": { "baseUrl": ".", "paths": { "@/": ["./src/"] } } }

**Tailwind classes not applying**:
```ts
// Ensure content paths include your components
// tailwind.config.ts
content: [
  './src/components/**/*.{ts,tsx}',  // Add this
  './src/app/**/*.{ts,tsx}',
]
Dark mode not working:
tsx
// Add suppressHydrationWarning to <html>
<html lang="en" suppressHydrationWarning>
Form validation not triggering:
tsx
// Ensure FormMessage is included in FormField
<FormField>
  <FormItem>
    <FormControl>...</FormControl>
    <FormMessage />  {/* Required for errors */}
  </FormItem>
</FormField>
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/": ["./src/"] } } }

**Tailwind类不生效**:
```ts
#确保content路径包含你的组件

Resources

tailwind.config.ts

content: [ './src/components//*.{ts,tsx}', // 添加此路径 './src/app//*.{ts,tsx}', ]

**深色模式不工作**:
```tsx

<html>标签添加suppressHydrationWarning

<html lang="en" suppressHydrationWarning> ```
表单验证不触发
tsx
undefined

确保FormField中包含FormMessage

<FormField> <FormItem> <FormControl>...</FormControl> <FormMessage /> {/* 错误提示需要此组件 */} </FormItem> </FormField> ```

资源