name: styling-with-shadcn description: | 在 Next.js 中使用 shadcn/ui 组件构建美观、可访问的用户界面。适用于创建表单、对话框、表格、侧边栏或任何 UI 组件。涵盖安装、组件模式、react-hook-form + Zod 验证和深色模式设置。 不适用于构建非 React 应用程序或使用其他组件库。
shadcn/ui
使用基于 Radix UI 和 Tailwind CSS 的复制粘贴组件,构建美观、可访问的用户界面。
快速开始
# 在您的 Next.js 项目中初始化 shadcn/ui
npx shadcn@latest init
# 按需添加组件
npx shadcn@latest add button form dialog table sidebar
常用组件安装
npx shadcn@latest add button card form input label dialog \
table badge sidebar dropdown-menu avatar separator \
select textarea tabs toast sonner
核心模式
1. 按钮变体
import { Button } from "@/components/ui/button"
<Button variant="default">主要</Button>
<Button variant="secondary">次要</Button>
<Button variant="destructive">删除</Button>
<Button variant="outline">轮廓</Button>
<Button variant="ghost">幽灵</Button>
// 尺寸: sm, default, lg, icon
<Button size="icon"><Plus /></Button>
// 加载状态
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
加载中...
</Button>
// 作为 Next.js 链接
<Button asChild>
<Link href="/dashboard">前往仪表板</Link>
</Button>
2. 使用 react-hook-form + Zod 的表单
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
const schema = z.object({
title: z.string().min(1, "必填"),
priority: z.enum(["low", "medium", "high"]),
})
export function TaskForm({ onSubmit }) {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { title: "", priority: "medium" },
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>标题</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">提交</Button>
</form>
</Form>
)
}
完整表单(包含 Select、Textarea)请参阅 references/component-examples.md。
3. 对话框 / 模态框
import {
Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>创建任务</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>创建新任务</DialogTitle>
<DialogDescription>向您的项目添加新任务。</DialogDescription>
</DialogHeader>
<TaskForm onSubmit={handleSubmit} />
</DialogContent>
</Dialog>
// 受控对话框
const [open, setOpen] = useState(false)
<Dialog open={open} onOpenChange={setOpen}>...</Dialog>
4. 警告对话框(确认)
import {
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader,
AlertDialogTitle, AlertDialogTrigger
} from "@/components/ui/alert-dialog"
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">删除</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>您确定吗?</AlertDialogTitle>
<AlertDialogDescription>此操作无法撤销。</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete}>删除</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
5. 数据表格 (TanStack)
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
const columns: ColumnDef<Task>[] = [
{ accessorKey: "title", header: "标题" },
{
accessorKey: "status",
header: "状态",
cell: ({ row }) => <Badge>{row.getValue("status")}</Badge>,
},
]
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
})
包含排序/分页的完整 DataTable 请参阅 references/component-examples.md。
6. 卡片组件
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
<Card>
<CardHeader>
<CardTitle>{task.title}</CardTitle>
<CardDescription>分配给 {task.assignee}</CardDescription>
</CardHeader>
<CardContent>
<p>{task.description}</p>
</CardContent>
<CardFooter>
<Button>开始</Button>
</CardFooter>
</Card>
7. 通知提示 (Sonner)
// 将 Toaster 添加到布局
import { Toaster } from "@/components/ui/sonner"
<Toaster />
// 在组件中使用
import { toast } from "sonner"
toast.success("任务已创建")
toast.error("创建任务失败")
toast("任务已更新", { description: "状态已更改为进行中" })
toast.promise(createTask(data), {
loading: "创建中...",
success: "已创建!",
error: "失败",
})
8. 侧边栏导航
import {
Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent,
SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger
} from "@/components/ui/sidebar"
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}><item.icon />{item.title}</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarContent>
</Sidebar>
<main><SidebarTrigger />{children}</main>
</SidebarProvider>
包含持久化状态的完整侧边栏请参阅 references/component-examples.md。
9. 深色模式
// npm install next-themes
// components/theme-provider.tsx
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
// layout.tsx
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
// 主题切换
import { useTheme } from "next-themes"
const { setTheme } = useTheme()
setTheme("dark") // 或 "light" 或 "system"
依赖项
{
"dependencies": {
"@hookform/resolvers": "^3.x",
"@radix-ui/react-*": "latest",
"@tanstack/react-table": "^8.x",
"class-variance-authority": "^0.7.x",
"clsx": "^2.x",
"lucide-react": "^0.x",
"next-themes": "^0.4.x",
"react-hook-form": "^7.x",
"sonner": "^1.x",
"tailwind-merge": "^2.x",
"zod": "^3.x"
}
}
验证
运行:python3 scripts/verify.py
预期结果:✓ styling-with-shadcn skill ready
如果验证失败
- 检查:references/ 文件夹是否存在,且包含 component-examples.md
- 如果仍然失败,请停止并报告
相关技能
- fetching-library-docs - 最新的 shadcn/ui 文档:
--library-id /shadcn-ui/ui --topic components - building-nextjs-apps - Next.js 16 的应用程序结构模式
参考资料
- references/component-examples.md - 完整代码示例
- references/taskflow-theme.md - 自定义主题配置