shadcn-ui-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThis skill provides strict, opinionated guidelines for building polished, production-grade UIs in projects that use shadcn/ui, Tailwind CSS, and Next.js.
本技能为使用shadcn/ui、Tailwind CSS和Next.js的项目提供严格、明确的规范,用于构建精致的生产级UI。
Package Manager
包管理器
ALWAYS use . Never use or .
pnpmnpmyarnbash
pnpm install
pnpm add package-name
pnpm dlx shadcn@latest add button始终使用,禁止使用或。
pnpmnpmyarnbash
pnpm install
pnpm add package-name
pnpm dlx shadcn@latest add buttonComponent Rules
组件规则
Use only shadcn/ui components. Never import directly from Radix UI primitives or other UI libraries (react-bootstrap, @mui/material, etc.).
tsx
// ✅ CORRECT
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
// ❌ WRONG
import * as Dialog from "@radix-ui/react-dialog"
import { Button } from "@mui/material"Install missing components with:
bash
pnpm dlx shadcn@latest add [component-name]仅使用shadcn/ui组件。禁止直接从Radix UI原语或其他UI库(如react-bootstrap、@mui/material等)导入组件。
tsx
// ✅ 正确写法
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
// ❌ 错误写法
import * as Dialog from "@radix-ui/react-dialog"
import { Button } from "@mui/material"安装缺失组件的命令:
bash
pnpm dlx shadcn@latest add [component-name]Available shadcn/ui Components
可用的shadcn/ui组件
Layout: Card, Separator, Tabs, Sheet, Collapsible
Forms: Button, Input, Textarea, Select, Checkbox, Radio Group, Switch, Slider, Form, Label
Data Display: Table, Badge, Avatar, Progress, Skeleton
Overlays: Dialog, Alert Dialog, Popover, Tooltip, Alert, Toast (Sonner), Dropdown Menu, Command, Drawer
Navigation: Navigation Menu, Breadcrumb, Pagination, Scroll Area
Forms: Button, Input, Textarea, Select, Checkbox, Radio Group, Switch, Slider, Form, Label
Data Display: Table, Badge, Avatar, Progress, Skeleton
Overlays: Dialog, Alert Dialog, Popover, Tooltip, Alert, Toast (Sonner), Dropdown Menu, Command, Drawer
Navigation: Navigation Menu, Breadcrumb, Pagination, Scroll Area
布局类:Card、Separator、Tabs、Sheet、Collapsible
表单类:Button、Input、Textarea、Select、Checkbox、Radio Group、Switch、Slider、Form、Label
数据展示类:Table、Badge、Avatar、Progress、Skeleton
浮层类:Dialog、Alert Dialog、Popover、Tooltip、Alert、Toast(Sonner)、Dropdown Menu、Command、Drawer
导航类:Navigation Menu、Breadcrumb、Pagination、Scroll Area
表单类:Button、Input、Textarea、Select、Checkbox、Radio Group、Switch、Slider、Form、Label
数据展示类:Table、Badge、Avatar、Progress、Skeleton
浮层类:Dialog、Alert Dialog、Popover、Tooltip、Alert、Toast(Sonner)、Dropdown Menu、Command、Drawer
导航类:Navigation Menu、Breadcrumb、Pagination、Scroll Area
Styling
样式设计
Use Tailwind CSS utility classes exclusively. No custom CSS files, no inline styles.
tsx
// ✅ CORRECT
<div className="flex items-center gap-4 p-6 rounded-lg border bg-card">
// ❌ WRONG
<div style={{ display: 'flex', padding: '24px' }}>Use shadcn design tokens for color:
tsx
bg-background text-foreground
bg-primary text-primary-foreground
bg-secondary text-secondary-foreground
bg-muted text-muted-foreground
bg-card text-card-foreground
bg-destructive text-destructive-foreground
border-border border-inputUse from for conditional classes.
cn()@/lib/utils仅使用Tailwind CSS工具类,禁止使用自定义CSS文件或内联样式。
tsx
// ✅ 正确写法
<div className="flex items-center gap-4 p-6 rounded-lg border bg-card">
// ❌ 错误写法
<div style={{ display: 'flex', padding: '24px' }}>使用shadcn设计令牌设置颜色:
tsx
bg-background text-foreground
bg-primary text-primary-foreground
bg-secondary text-secondary-foreground
bg-muted text-muted-foreground
bg-card text-card-foreground
bg-destructive text-destructive-foreground
border-border border-input使用中的处理条件类。
@/lib/utilscn()Notifications
通知组件
Use Sonner for all notifications. Never use or other toast libraries.
react-hot-toasttsx
import { toast } from "sonner"
toast.success("Saved successfully!")
toast.error("Something went wrong")
toast.loading("Saving...")
toast.promise(saveData(), {
loading: "Saving...",
success: "Saved!",
error: "Failed to save"
})Add from to the root layout.
<Toaster />@/components/ui/sonner所有通知均使用Sonner,禁止使用或其他Toast库。
react-hot-toasttsx
import { toast } from "sonner"
toast.success("保存成功!")
toast.error("出现错误")
toast.loading("保存中...")
toast.promise(saveData(), {
loading: "保存中...",
success: "保存完成!",
error: "保存失败"
})在根布局中添加中的组件。
@/components/ui/sonner<Toaster />Forms
表单实现
Use shadcn Form + React Hook Form + Zod for all forms.
tsx
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
const schema = z.object({ email: z.string().email() })
export function MyForm() {
const form = useForm({ resolver: zodResolver(schema) })
return (
<Form {...form}>
<form onSubmit={form.handleSubmit((v) => console.log(v))} className="space-y-6">
<FormField control={form.control} name="email" render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit">Submit</Button>
</form>
</Form>
)
}所有表单均使用shadcn Form + React Hook Form + Zod组合。
tsx
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
const schema = z.object({ email: z.string().email() })
export function MyForm() {
const form = useForm({ resolver: zodResolver(schema) })
return (
<Form {...form}>
<form onSubmit={form.handleSubmit((v) => console.log(v))} className="space-y-6">
<FormField control={form.control} name="email" render={({ field }) => (
<FormItem>
<FormLabel>邮箱</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit">提交</Button>
</form>
</Form>
)
}Typography & Fonts
排版与字体
Use modern fonts. Recommended choices: DM Sans, Public Sans, Plus Jakarta Sans, Outfit. Avoid Inter, Roboto, Arial.
tsx
// app/layout.tsx
import { DM_Sans } from "next/font/google"
const dmSans = DM_Sans({ subsets: ["latin"], weight: ["400","500","600","700"], variable: "--font-dm-sans" })
// tailwind.config.ts
fontFamily: { sans: ["var(--font-dm-sans)", "system-ui", "sans-serif"] }Typography scale:
tsx
<h1 className="text-4xl font-bold tracking-tight">Heading</h1>
<h2 className="text-2xl font-semibold">Section</h2>
<p className="text-base text-muted-foreground">Body</p>
<span className="text-xs font-medium uppercase tracking-wide">Label</span>使用现代字体,推荐选择:DM Sans、Public Sans、Plus Jakarta Sans、Outfit。避免使用Inter、Roboto、Arial。
tsx
// app/layout.tsx
import { DM_Sans } from "next/font/google"
const dmSans = DM_Sans({ subsets: ["latin"], weight: ["400","500","600","700"], variable: "--font-dm-sans" })
// tailwind.config.ts
fontFamily: { sans: ["var(--font-dm-sans)", "system-ui", "sans-serif"] }排版层级示例:
tsx
<h1 className="text-4xl font-bold tracking-tight">标题</h1>
<h2 className="text-2xl font-semibold">章节标题</h2>
<p className="text-base text-muted-foreground">正文</p>
<span className="text-xs font-medium uppercase tracking-wide">标签</span>Theming (globals.css) — OKLCH Colors
主题设置(globals.css)—— OKLCH颜色空间
CRITICAL: Always use OKLCH color space for all CSS custom properties. Never use HSL, RGB, or hex values for theme tokens. OKLCH provides perceptually uniform lightness, better color interpolation, and access to wide-gamut P3 colors.
关键要求:所有CSS自定义属性必须始终使用OKLCH颜色空间,禁止为主题令牌使用HSL、RGB或十六进制值。OKLCH提供感知均匀的亮度、更好的颜色插值效果,并且支持广色域P3颜色。
Why OKLCH
为什么选择OKLCH
- Perceptually uniform — changing lightness actually looks uniform across hues
- Wide-gamut support — can express colors beyond sRGB (P3, Rec2020)
- Better for generating consistent tints/shades programmatically
- Native in all modern browsers; Tailwind v4 uses it by default
- 感知均匀——调整亮度时,所有色调的视觉效果保持一致
- 支持广色域——可以表达sRGB之外的颜色(如P3、Rec2020)
- 更便于程序化生成一致的色调/阴影
- 所有现代浏览器原生支持;Tailwind v4默认使用该颜色空间
OKLCH Syntax
OKLCH语法
css
oklch(L C H)
/* L = lightness 0–1 (0 = black, 1 = white) */
/* C = chroma 0–0.4 (0 = gray, higher = more saturated) */
/* H = hue 0–360 (degrees on color wheel) */css
oklch(L C H)
/* L = 亮度 0–1 (0=黑色,1=白色) */
/* C = 色度 0–0.4 (0=灰色,值越高饱和度越高) */
/* H = 色相 0–360 (色轮上的度数) */Full globals.css Template
完整globals.css模板
css
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
:root {
/* Neutral base */
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
/* Primary accent — change H to shift the hue */
--primary: oklch(0.55 0.22 29); /* e.g. vivid red-orange */
--primary-foreground: oklch(0.985 0.01 29);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.3);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.55 0.22 29);
--radius: 0.5rem;
/* Charts */
--chart-1: oklch(0.646 0.222 41.1);
--chart-2: oklch(0.6 0.118 184.7);
--chart-3: oklch(0.398 0.07 227.4);
--chart-4: oklch(0.828 0.189 84.4);
--chart-5: oklch(0.769 0.188 70.1);
/* Sidebar */
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.55 0.22 29);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.55 0.22 29);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.55 0.22 29);
--primary-foreground: oklch(0.985 0.01 29);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.7);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.55 0.22 29);
/* Charts — dark */
--chart-1: oklch(0.488 0.243 264.4);
--chart-2: oklch(0.696 0.17 162.5);
--chart-3: oklch(0.769 0.188 70.1);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.4);
/* Sidebar — dark */
--sidebar: oklch(0.145 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.55 0.22 29);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.55 0.22 29);
}
@layer base {
* { @apply border-border; }
body { @apply bg-background text-foreground antialiased; }
html { scroll-behavior: smooth; }
:focus-visible { @apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background; }
}css
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
:root {
/* 中性基础色 */
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
/* 主强调色——修改H值可切换色相 */
--primary: oklch(0.55 0.22 29); /* 例如:鲜艳的红橙色 */
--primary-foreground: oklch(0.985 0.01 29);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.3);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.55 0.22 29);
--radius: 0.5rem;
/* 图表颜色 */
--chart-1: oklch(0.646 0.222 41.1);
--chart-2: oklch(0.6 0.118 184.7);
--chart-3: oklch(0.398 0.07 227.4);
--chart-4: oklch(0.828 0.189 84.4);
--chart-5: oklch(0.769 0.188 70.1);
/* 侧边栏颜色 */
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.55 0.22 29);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.55 0.22 29);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.55 0.22 29);
--primary-foreground: oklch(0.985 0.01 29);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.7);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.55 0.22 29);
/* 图表颜色——深色模式 */
--chart-1: oklch(0.488 0.243 264.4);
--chart-2: oklch(0.696 0.17 162.5);
--chart-3: oklch(0.769 0.188 70.1);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.4);
/* 侧边栏颜色——深色模式 */
--sidebar: oklch(0.145 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.55 0.22 29);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.55 0.22 29);
}
@layer base {
* { @apply border-border; }
body { @apply bg-background text-foreground antialiased; }
html { scroll-behavior: smooth; }
:focus-visible { @apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background; }
}Changing the Accent Color
修改强调色
Only touch the , , and variables. Change the H (hue) value to shift the accent across the color wheel:
--primary--primary-foreground--ring| Color | H value | Example OKLCH |
|---|---|---|
| Red | 29 | |
| Orange | 60 | |
| Yellow | 90 | |
| Green | 145 | |
| Teal | 185 | |
| Blue | 250 | |
| Violet | 290 | |
| Pink | 340 | |
仅修改、和变量。通过修改**H(色相)**值在色轮上切换强调色:
--primary--primary-foreground--ring| 颜色 | H值 | OKLCH示例 |
|---|---|---|
| 红色 | 29 | |
| 橙色 | 60 | |
| 黄色 | 90 | |
| 绿色 | 145 | |
| 蓝绿色 | 185 | |
| 蓝色 | 250 | |
| 紫色 | 290 | |
| 粉色 | 340 | |
Never Use These in Theme Tokens
主题令牌中禁止使用的写法
css
/* ❌ WRONG */
--primary: #e53e3e;
--primary: rgb(229, 62, 62);
--primary: hsl(0, 72%, 50%);
/* ✅ CORRECT */
--primary: oklch(0.55 0.22 29);css
/* ❌ 错误写法 */
--primary: #e53e3e;
--primary: rgb(229, 62, 62);
--primary: hsl(0, 72%, 50%);
/* ✅ 正确写法 */
--primary: oklch(0.55 0.22 29);Polished UI Checklist
精致UI检查清单
Before shipping any component, verify:
- Consistent padding/margins using Tailwind spacing scale
- Uniform border-radius across elements
- Subtle shadows (,
shadow-sm) for depthshadow-md - Smooth transitions () on interactive elements
transition-all duration-200 - Hover and focus states on all interactive elements
- Loading states handled with or
<Skeleton>toast.loading() - Destructive actions gated behind
<AlertDialog> - Responsive layout using Tailwind breakpoints (,
sm:,md:)lg: - Design tokens used for all colors (not raw hex or rgb values)
在交付任何组件前,验证以下内容:
- 使用Tailwind间距刻度保持一致的内边距/外边距
- 所有元素的圆角保持统一
- 使用细微阴影(、
shadow-sm)增加层次感shadow-md - 交互元素添加平滑过渡()
transition-all duration-200 - 所有交互元素包含悬停和聚焦状态
- 使用或
<Skeleton>处理加载状态toast.loading() - 破坏性操作需通过确认
<AlertDialog> - 使用Tailwind断点(、
sm:、md:)实现响应式布局lg: - 所有颜色使用设计令牌(禁止使用原始十六进制或RGB值)
Common Patterns
常见模式示例
Data Table
数据表格
tsx
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
export function DataTable({ data }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell><Badge>{item.status}</Badge></TableCell>
<TableCell><Button variant="ghost" size="sm">Edit</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}tsx
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
export function DataTable({ data }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>名称</TableHead>
<TableHead>状态</TableHead>
<TableHead>操作</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell><Badge>{item.status}</Badge></TableCell>
<TableCell><Button variant="ghost" size="sm">编辑</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}Dialog with Form
带表单的对话框
tsx
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function CreateDialog() {
return (
<Dialog>
<DialogTrigger asChild><Button>Create New</Button></DialogTrigger>
<DialogContent>
<DialogHeader><DialogTitle>Create Item</DialogTitle></DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Enter name" />
</div>
</div>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Create</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}tsx
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function CreateDialog() {
return (
<Dialog>
<DialogTrigger asChild><Button>创建新项</Button></DialogTrigger>
<DialogContent>
<DialogHeader><DialogTitle>创建新项</DialogTitle></DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name">名称</Label>
<Input id="name" placeholder="输入名称" />
</div>
</div>
<DialogFooter>
<Button variant="outline">取消</Button>
<Button>创建</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Loading Skeleton
加载骨架屏
tsx
import { Skeleton } from "@/components/ui/skeleton"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
export function LoadingCard() {
return (
<Card>
<CardHeader><Skeleton className="h-4 w-[250px]" /></CardHeader>
<CardContent className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[200px]" />
</CardContent>
</Card>
)
}tsx
import { Skeleton } from "@/components/ui/skeleton"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
export function LoadingCard() {
return (
<Card>
<CardHeader><Skeleton className="h-4 w-[250px]" /></CardHeader>
<CardContent className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[200px]" />
</CardContent>
</Card>
)
}Prohibited Practices
禁止的操作
| ❌ Never Do | ✅ Do Instead |
|---|---|
| |
| Use Tailwind classes |
| |
| |
| |
| |
| ❌ 禁止做法 | ✅ 正确做法 |
|---|---|
| |
| 使用Tailwind工具类 |
| |
| |
| |
| |