很多开发者对"设计"的理解停留在"画个好看的界面",但实际上在 SaaS 产品中,设计工具链解决的是三个核心问题:
20 个页面 20 种按钮样式?没有 Design System,团队越大越混乱。设计工具的首要任务是建立并执行视觉规范。
设计师在 Figma 里画完,开发者要花 3 天还原。设计工具链的终极目标是缩小设计稿到可运行代码的差距。
从 5 个页面到 50 个页面,你的设计能否保持一致?Design Token + Component Library 是线性扩展的基石。
改个品牌色要改 200 个文件?Token 驱动的设计系统让全局变更变成一行配置的事。
2026 年的设计工具栈已经远超"画图软件"的范畴——它是一个从设计决策到代码实现的完整管道。
Figma 是基于浏览器的协作式界面设计工具,几乎垄断了产品设计领域。它不只是"画图",而是一个完整的设计操作系统——支持组件系统、自动布局、变体、设计 Token、原型交互、开发者交付,以及庞大的插件生态。
Figma 的组件系统支持 Component → Variant → Property 三层抽象,能表达按钮的 size/variant/state 等维度:
这种"组件即函数"的模式,和代码中的 React Component 高度同构。
Figma 的 Auto Layout 本质上是 CSS Flexbox 的可视化编辑器:
理解 Auto Layout = 理解 Flexbox,开发还原成本趋近于零。
Figma Variables 是 Design Token 的原生实现:
Color — 颜色 Token(如 --color-primary)Number — 间距/圆角/字号(如 --spacing-md)Boolean — 组件状态开关String — 文本内容 TokenVariables 可通过 Figma API 导出,直接映射到代码中的 CSS 变量或 Tailwind Token。
Figma 拥有数千插件,关键类别:
2023 年推出的 Dev Mode 让开发者有独立的查看视角:
Penpot 是 Figma 的开源替代,核心差异:
适合:开源团队、数据敏感场景、预算有限的小团队。
Sketch 是最早的 UI 设计工具之一,但已式微:
Framer 的独特定位:
Tailwind CSS 是一个实用优先(Utility-First)的 CSS 框架,提供大量原子化的 CSS class(如 flex、pt-4、text-center、rotate-90),让你直接在 HTML 中组合样式,而不是写自定义 CSS。
在 2026 年,Tailwind 已经成为 SaaS 前端的事实标准——Next.js 官方模板默认 Tailwind,shadcn/ui 基于 Tailwind 构建,Vercel 生态全面拥抱。
传统 CSS:抽象组件类 .card { padding: 1rem; background: white; border-radius: 8px; }
Tailwind:原子组合 class="p-4 bg-white rounded-lg"
关键洞察:大多数人反对 Tailwind 的理由是"HTML 里一堆 class 看着乱",但真正的问题是——你在 HTML 里看到的是最终的样式决策,而不是藏在某个 CSS 文件里的间接引用。这种透明度在团队协作中反而减少了上下文切换。
Tailwind 的 tailwind.config.js 就是 Design Token 的代码实现:
// tailwind.config.ts
import type { Config } from 'tailwindcss'
const config: Config = {
theme: {
extend: {
colors: {
// 与 Figma Variables 一一对应
primary: {
50: '#eef2ff',
100: '#e0e7ff',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
900: '#312e81',
},
surface: {
DEFAULT: '#0a0b10',
card: '#13151e',
hover: '#1a1d2b',
}
},
spacing: {
// 8px 网格系统
'18': '4.5rem',
'88': '22rem',
},
borderRadius: {
'4xl': '2rem',
},
fontSize: {
'2xs': ['0.625rem', { lineHeight: '0.9rem' }],
}
}
},
plugins: [],
}
export default config
Tailwind 的 dark: 前缀让主题切换极其简单:
<!-- 自动适配暗色模式 -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<h1 class="text-2xl font-bold">标题</h1>
<p class="text-gray-500 dark:text-gray-400">描述文本</p>
</div>
策略选择:media(跟随系统)vs class(手动控制)。SaaS 产品推荐 class,因为需要用户主动切换。
<!-- Mobile-first 响应式 -->
<div class="
grid grid-cols-1 /* 手机:单列 */
md:grid-cols-2 /* 平板:双列 */
lg:grid-cols-3 /* 桌面:三列 */
gap-4 md:gap-6 lg:gap-8 /* 间距递增 */
">
{items.map(item => <Card key={item.id} />)}
</div>
Tailwind 不是让你永远写原子 class,而是提供了正确的抽象层级:
// 1. 组件抽取(最常见)
function Button({ variant = 'primary', size = 'md', children }) {
const base = 'inline-flex items-center justify-center rounded-lg font-medium transition-colors'
const variants = {
primary: 'bg-primary-600 text-white hover:bg-primary-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
ghost: 'text-gray-600 hover:bg-gray-100',
}
const sizes = {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
}
return (
<button className={`${base} ${variants[variant]} ${sizes[size]}`}>
{children}
</button>
)
}
// 2. @apply(少用,仅用于无法组件化的场景)
// 在 CSS 文件中:
// .btn-primary { @apply bg-primary-600 text-white rounded-lg px-4 py-2; }
// 3. Tailwind Variants / cva(推荐,类型安全)
import { cva } from 'class-variance-authority'
const button = cva('inline-flex rounded-lg font-medium transition-colors', {
variants: {
variant: {
primary: 'bg-primary-600 text-white hover:bg-primary-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-sm',
}
},
defaultVariants: { variant: 'primary', size: 'md' }
})
| 方案 | 理念 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| Tailwind CSS | 实用优先 | 快速开发、Design Token、tree-shake | HTML 膨胀、学习曲线 | SaaS 产品、React 项目 |
| CSS Modules | 局部作用域 | 零运行时、原生 CSS、易理解 | 无内置 Design Token、需手写媒体查询 | 简单组件库、传统 React |
| Styled Components | CSS-in-JS | JS 全能力、动态样式 | 运行时开销、SSR 复杂、包体积大 | 高度动态 UI(已过时趋势) |
| Panda CSS | 编译时 CSS-in-JS | 零运行时、类型安全、Recipes | 生态较新、社区较小 | TypeScript 重度用户 |
| Vanilla Extract | TypeScript CSS | 完全类型安全、零运行时 | 写法繁琐、调试体验差 | 对类型安全有极致要求 |
| Open Props | CSS 变量优先 | 超轻量、原生 CSS、无构建步骤 | 无组件抽象、手动组合 | 轻量页面、原型 |
Design System 不只是"组件库"——它是一套可复用的设计决策系统,包括 Token、组件、模式、文档、和使用指南。好的 Design System 让团队不再重复做设计决策,而是"用已有的决策组合新功能"。
Design Token 是 Design System 的核心,它是设计决策的变量化:
// tokens/color.ts — 颜色 Token
export const colors = {
// 基础色板(来自 Figma Variables)
slate: {
50: '#f8fafc', 100: '#f1f5f9', 200: '#e2e8f0',
300: '#cbd5e1', 400: '#94a3b8', 500: '#64748b',
600: '#475569', 700: '#334155', 800: '#1e293b',
900: '#0f172a', 950: '#020617',
},
// 语义色(业务含义 > 色值本身)
interactive: {
default: 'slate.800',
hover: 'slate.700',
active: 'slate.900',
disabled: 'slate.300',
},
feedback: {
success: 'emerald.500',
warning: 'amber.500',
error: 'red.500',
info: 'blue.500',
},
} as const
// tokens/spacing.ts — 间距 Token
export const spacing = {
0: '0px',
1: '4px', // 基本单位 4px
2: '8px',
3: '12px',
4: '16px',
5: '20px',
6: '24px',
8: '32px',
10: '40px',
12: '48px',
16: '64px',
20: '80px',
} as const
// tokens/typography.ts — 排版 Token
export const typography = {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
} as const
// 使用 Style Dictionary 将 Token 转换为多平台输出
// config.json
{
"source": ["tokens/**/*.ts"],
"platforms": {
"css": {
"transformGroup": "css",
"buildPath": "dist/css/",
"files": [{
"destination": "tokens.css",
"format": "css/variables",
"options": { "outputReferences": true }
}]
},
"tailwind": {
"transformGroup": "js",
"buildPath": "dist/tailwind/",
"files": [{
"destination": "tokens.js",
"format": "javascript/es6"
}]
},
"ios": {
"transformGroup": "ios-swift",
"buildPath": "dist/ios/",
"files": [{
"destination": "DesignTokens.swift",
"format": "ios-swift/class.swift"
}]
}
}
}
每个组件都需要文档,最佳实践是使用 Storybook + MDX:
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost', 'danger'],
description: '按钮变体',
table: {
defaultValue: { summary: 'primary' },
},
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
description: '尺寸',
},
},
}
export default meta
type Story = StoryObj<typeof Button>
export const Primary: Story = {
args: { variant: 'primary', children: '主要按钮' },
}
export const AllVariants: Story = {
render: () => (
<div className="flex gap-4">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
</div>
),
}
shadcn/ui 不是传统的组件库——它不是一个 npm 包。它是一组可复制到项目中的高质量 React 组件代码,基于 Radix UI 原语 + Tailwind CSS + class-variance-authority 构建。你通过 CLI 拷贝组件到项目中,拥有完全控制权。
import { Button } from 'antd'(黑盒,升级受制于库版本)。shadcn/ui = 代码在你的项目中(白盒,你决定一切)。
没有额外的抽象层。组件代码就在你项目里,你可以直接修改任何细节——不需要 fork、不需要 override、不需要 !important。
Tailwind + CVA 让样式修改极其简单。想改圆角?改一行 class。想加动画?加一行 class。不需要和组件库的设计决策作斗争。
基于 Radix UI 原语,键盘导航、ARIA、焦点管理全部内置。你不需要自己处理 Modal 的焦点陷阱或 Dropdown 的键盘交互。
只添加你用到的组件。不需要的组件不会出现在 bundle 中。没有 antd 那样的 2MB 全量包。
# 初始化
npx shadcn-ui@latest init
# 添加组件
npx shadcn-ui@latest add button
npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add table
npx shadcn-ui@latest add form # 包含 react-hook-form + zod 集成
# 生成的文件在 components/ui/ 下
# 你可以直接编辑它们!
// components/ui/button.tsx — shadcn/ui 生成的按钮组件
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-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",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
| 特性 | shadcn/ui | Ant Design | MUI | Chakra UI |
|---|---|---|---|---|
| 分发方式 | 拷贝源码 | npm 包 | npm 包 | npm 包 |
| 样式方案 | Tailwind | CSS-in-JS (cssinjs) | Emotion/sx | CSS-in-JS |
| 定制难度 | 极低(直接改代码) | 中高(需 ConfigProvider) | 中等(sx/theme override) | 中等(chakra theme) |
| Bundle 大小 | 按需 | 全量 2MB+ | 按需(需配置) | 中等 |
| 暗色模式 | 内置 | 5.0+ 支持 | 支持 | 支持 |
| 表单集成 | react-hook-form + zod | Ant Design Form | React Hook Form | React Hook Form |
| 设计风格 | 极简现代 | 企业级(偏重) | Material Design | 极简现代 |
| 适合场景 | SaaS/现代 Web | 后台管理系统 | 通用/Material 风格 | 快速原型 |
shadcn/ui 默认图标库,Figma 同款风格(Feather Icons 的活跃 fork):
import { Settings, User, ChevronRight } from 'lucide-react'
function NavItem({ icon: Icon, label }) {
return (
<button className="flex items-center gap-2 px-3 py-2 hover:bg-gray-100 rounded-lg">
<Icon className="h-4 w-4" />
<span>{label}</span>
<ChevronRight className="h-4 w-4 ml-auto text-gray-400" />
</button>
)
}
| 方案 | 图标数 | 风格 | 格式 | 适合 |
|---|---|---|---|---|
| Lucide | 1,500+ | 线条 | SVG/React | SaaS 默认 |
| Heroicons | 300+ | 线条/实心 | SVG/React | Tailwind 官方推荐 |
| Phosphor | 7,000+ | 6 种粗细 | SVG/React | 最丰富的风格变化 |
| Tabler | 5,000+ | 线条 | SVG/React | 数量最多 |
| Iconify | 200,000+ | 混合 | API 按需 | 需要跨库混合 |
Next.js <Image> 组件自动处理:
v0.dev 可以通过自然语言描述生成 React + Tailwind + shadcn/ui 组件代码:
// Prompt: "创建一个 SaaS 定价卡片,包含三个等级:免费/专业/企业,专业版高亮推荐"
// v0 生成的代码(可直接使用)
function PricingCard() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl mx-auto">
{plans.map((plan) => (
<div key={plan.name} className={cn(
"rounded-xl border p-6",
plan.featured
? "border-primary bg-primary/5 ring-2 ring-primary"
: "border-border bg-card"
)}>
{plan.featured && (
<Badge className="mb-4">推荐</Badge>
)}
<h3 className="text-xl font-bold">{plan.name}</h3>
<div className="mt-4">
<span className="text-4xl font-bold">¥{plan.price}</span>
{plan.price > 0 && <span className="text-muted-foreground">/月</span>}
</div>
<Button className="w-full mt-6" variant={plan.featured ? "default" : "outline"}>
{plan.price === 0 ? "免费开始" : "立即订阅"}
</Button>
<ul className="mt-6 space-y-3">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-2 text-sm">
<Check className="h-4 w-4 text-primary" /> {f}
</li>
))}
</ul>
</div>
))}
</div>
)
}
| 工具 | 输入 | 输出 | 代码质量 | 适合 |
|---|---|---|---|---|
| v0 | 文本/图片 | React + shadcn | 高(可生产用) | 组件级原型 |
| Galileo AI | 文本 | Figma 设计稿 | 中等 | 设计探索 |
| Uizard | 文本/草图 | 设计稿 + 代码 | 低-中 | 快速线框图 |
| Midjourney | 文本 | 图片 | N/A(视觉参考) | 品牌视觉/插画 |
| DALL-E | 文本 | 图片 | N/A | 图标/插画概念 |
| Bolt.new | 文本 | 完整 Web 应用 | 中等 | MVP 验证 |
没有专职设计师?跳过 Figma,直接用代码设计:
// 1. 用 shadcn/ui 组装页面
// 2. 用 Tailwind 实时调整样式
// 3. 用 Storybook 做组件文档
// 4. 用 v0 生成初始结构
// 示例:10 分钟搭出 Dashboard 骨架
export default function Dashboard() {
return (
<div className="flex h-screen">
{/* 侧边栏 */}
<aside className="w-64 border-r bg-card p-4">
<div className="flex items-center gap-2 mb-8">
<Logo className="h-8 w-8" />
<span className="font-bold text-lg">MySaaS</span>
</div>
<nav className="space-y-1">
{navItems.map(item => (
<NavItem key={item.href} {...item} />
))}
</nav>
</aside>
{/* 主内容 */}
<main className="flex-1 overflow-auto">
<header className="border-b px-6 py-4 flex items-center justify-between">
<h1 className="text-xl font-bold">概览</h1>
<div className="flex items-center gap-4">
<Button variant="outline" size="sm">导出</Button>
<Avatar><AvatarImage src={user.avatar} /></Avatar>
</div>
</header>
<div className="p-6">
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<StatCard title="收入" value="¥128,400" change={+12.5} />
<StatCard title="用户" value="2,847" change={+8.2} />
<StatCard title="转化率" value="3.24%" change={-0.8} />
</div>
{/* 图表区域 */}
<Card>
<CardHeader>
<CardTitle>收入趋势</CardTitle>
</CardHeader>
<CardContent>
<RevenueChart />
</CardContent>
</Card>
</div>
</main>
</div>
)
}
// 使用 Playwright 做组件截图对比
import { test, expect } from '@playwright/test'
test('PricingCard 视觉回归', async ({ page }) => {
await page.goto('/components/pricing-card')
const card = page.locator('[data-testid="pricing-card"]')
await expect(card).toHaveScreenshot('pricing-card.png', {
maxDiffPixelRatio: 0.01, // 允许 1% 像素差异
})
})
// CI 中自动运行
// .github/workflows/visual.yml
// on: [pull_request]
// jobs:
// visual:
// runs-on: ubuntu-latest
// steps:
// - uses: actions/checkout@v4
// - run: npm ci && npx playwright install
// - run: npx playwright test --update-snapshots
// - uses: actions/upload-artifact@v4 # 失败时上传 diff
推荐:Code-First 设计
├── Tailwind CSS + shadcn/ui(直接在代码中设计)
├── v0.dev(AI 生成初始布局)
├── Excalidraw(快速线框图)
├── Lucide Icons
└── 跳过 Figma
推荐:Figma + Code 双轨
├── Figma(设计稿 + 组件库 + Dev Mode 交付)
├── Tailwind CSS + shadcn/ui(代码实现)
├── Figma Variables → Style Dictionary → Tailwind Config
├── Storybook(组件文档 + 视觉回归)
└── Chromatic(自动化视觉测试)
推荐:完整 Design System 管道
├── Figma Enterprise(分支、团队库、分析)
├── Design Token Pipeline(Figma → Style Dictionary → 多平台)
├── Storybook + Chromatic(文档 + 回归)
├── 自建组件库(基于 Radix + Tailwind + CVA)
├── Figma Plugin 自动同步 Token
└── 专职 Design System 团队维护
| 类别 | 首选 | 替代 | 预算 |
|---|---|---|---|
| 界面设计 | Figma | Penpot (开源) | $0-45/月/人 |
| CSS 框架 | Tailwind CSS | Panda CSS / CSS Modules | 免费 |
| 组件库 | shadcn/ui | Radix + 自建 / Ant Design | 免费 |
| Token 管理 | Style Dictionary | Tokens Studio / Diez | 免费 |
| 图标 | Lucide | Phosphor / Heroicons | 免费 |
| 线框图 | Excalidraw | tldraw / Balsamiq | 免费-$12/月 |
| 原型交互 | Figma Prototype | Protopie / Principle | 含在 Figma |
| 组件文档 | Storybook | Docusaurus / Vitebook | 免费 |
| 视觉回归 | Chromatic | Percy / Playwright screenshots | $0-149/月 |
| AI 生成 | v0.dev | Bolt.new / Galileo | $0-20/月 |
| 无障碍检查 | axe DevTools | Stark / Lighthouse | 免费基础版 |
产品还在探索期就花 2 个月建 Design System?大概率你的 Token 和组件会在 3 个月后全部推翻。
正确做法:先用 shadcn/ui + Tailwind 的默认 Token,等产品形态稳定后再抽取自己的 Design System。通常在 5-10 个页面之后你才能看清模式。
设计师在 Figma 更新了按钮圆角,开发者的代码还是旧的?这是最常见的协作问题。
解决方案:
2026 年新项目还在用 styled-components?运行时 CSS-in-JS 已经被业界抛弃。
为什么:SSR 性能问题、bundle 体积、hydration 不匹配。Next.js 官方也不推荐。
替代:Tailwind(实用优先)或 Panda CSS(编译时 CSS-in-JS,类型安全)。
90% 的开发者只在亮色模式下开发。结果暗色模式下对比度不足、图片没有适配、颜色硬编码。
规则:每个组件必须同时测试亮色和暗色。用 dark: 前缀,用 CSS 变量,不硬编码颜色值。
v0 生成的代码直接上线?你可能得到一个看起来不错但违反你 Design System 的组件。
正确做法:AI 生成 → 用你的 Design Token 替换硬编码值 → 用你的组件库替换重复组件 → 测试无障碍。
Ant Design 适合后台管理系统,但它的设计语言太重、定制太难、bundle 太大。SaaS 面向用户的产品需要更轻量、更现代的设计。
替代:shadcn/ui(轻量 + 可定制)或 Radix + Tailwind 自建。
很多团队把无障碍当成"后续优化",但 WCAG 2.1 AA 是很多地区的法律要求(包括欧盟的 European Accessibility Act 2025)。
最低标准:颜色对比度 4.5:1、键盘可导航、所有交互元素有 focus 样式、图片有 alt 文本。