🎯 设计工具与 Design System

从 Figma 到代码:SaaS 产品设计工具链、Design System 构建方法论、以及 AI 时代的设计工作流变革
📑 目录

设计工具全景:为什么设计不只是"画 UI"

很多开发者对"设计"的理解停留在"画个好看的界面",但实际上在 SaaS 产品中,设计工具链解决的是三个核心问题:

🎨 视觉一致性

20 个页面 20 种按钮样式?没有 Design System,团队越大越混乱。设计工具的首要任务是建立并执行视觉规范

🔄 设计到代码的鸿沟

设计师在 Figma 里画完,开发者要花 3 天还原。设计工具链的终极目标是缩小设计稿到可运行代码的差距

📐 可扩展性

从 5 个页面到 50 个页面,你的设计能否保持一致?Design Token + Component Library 是线性扩展的基石

⚡ 迭代速度

改个品牌色要改 200 个文件?Token 驱动的设计系统让全局变更变成一行配置的事

2026 年的设计工具栈已经远超"画图软件"的范畴——它是一个从设计决策到代码实现的完整管道。

💡 核心认知:设计工具的选择不是审美偏好,而是工程决策。选择 Figma 还是 Penpot、Tailwind 还是 CSS Modules、shadcn 还是 Ant Design——这些决策会影响团队协作流程、代码维护成本、和产品扩展能力。

Figma — 设计协作的行业标准

是什么

Figma 是基于浏览器的协作式界面设计工具,几乎垄断了产品设计领域。它不只是"画图",而是一个完整的设计操作系统——支持组件系统、自动布局、变体、设计 Token、原型交互、开发者交付,以及庞大的插件生态。

核心能力

🏗️ 组件与变体系统

Figma 的组件系统支持 ComponentVariantProperty 三层抽象,能表达按钮的 size/variant/state 等维度:

这种"组件即函数"的模式,和代码中的 React Component 高度同构。

📐 Auto Layout

Figma 的 Auto Layout 本质上是 CSS Flexbox 的可视化编辑器

理解 Auto Layout = 理解 Flexbox,开发还原成本趋近于零。

🎛️ Design Token (Variables)

Figma Variables 是 Design Token 的原生实现:

Variables 可通过 Figma API 导出,直接映射到代码中的 CSS 变量或 Tailwind Token。

🔌 插件生态

Figma 拥有数千插件,关键类别:

Figma 开发者模式

2023 年推出的 Dev Mode 让开发者有独立的查看视角:

何时用 Figma

✅ 适合
  • 有专职设计师的团队
  • 需要多人实时协作
  • 复杂的多页面产品设计
  • 需要组件库和 Design Token
  • 需要原型交互演示
❌ 不适合
  • 纯开发者独立项目(过度设计)
  • 对数据隐私有严格要求的场景
  • 离线工作需求(Figma 桌面版有限离线)
  • 不想付费(免费版限制 3 个文件)

替代方案

🆚 Penpot — 开源替代

Penpot 是 Figma 的开源替代,核心差异:

适合:开源团队、数据敏感场景、预算有限的小团队。

🆚 Sketch — macOS 原生老牌

Sketch 是最早的 UI 设计工具之一,但已式微:

🆚 Framer — 设计即代码

Framer 的独特定位:

Tailwind CSS — 实用优先的 CSS 框架

是什么

Tailwind CSS 是一个实用优先(Utility-First)的 CSS 框架,提供大量原子化的 CSS class(如 flexpt-4text-centerrotate-90),让你直接在 HTML 中组合样式,而不是写自定义 CSS。

在 2026 年,Tailwind 已经成为 SaaS 前端的事实标准——Next.js 官方模板默认 Tailwind,shadcn/ui 基于 Tailwind 构建,Vercel 生态全面拥抱。

核心哲学

🧱 原子化 vs 抽象化

传统 CSS:抽象组件类 .card { padding: 1rem; background: white; border-radius: 8px; }

Tailwind:原子组合 class="p-4 bg-white rounded-lg"

关键洞察:大多数人反对 Tailwind 的理由是"HTML 里一堆 class 看着乱",但真正的问题是——你在 HTML 里看到的是最终的样式决策,而不是藏在某个 CSS 文件里的间接引用。这种透明度在团队协作中反而减少了上下文切换。

核心特性

Design Token 集成

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

✅ 适合
  • SaaS 产品(与 shadcn/ui 完美配合)
  • 需要快速迭代的项目
  • 团队已有 React/Next.js 技术栈
  • 需要 Design Token 驱动
  • 响应式 + 暗色模式需求
❌ 不适合
  • 纯内容站点(Astro + CSS 更轻量)
  • 团队强烈偏好 CSS-in-JS
  • 需要极精细的动画控制(GSAP/Framer Motion 更好)
  • 传统多页应用(Tailwind 4 JIT 需构建步骤)

替代方案对比

方案 理念 优点 缺点 适合场景
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 — 从零构建可扩展的设计系统

是什么

Design System 不只是"组件库"——它是一套可复用的设计决策系统,包括 Token、组件、模式、文档、和使用指南。好的 Design System 让团队不再重复做设计决策,而是"用已有的决策组合新功能"。

Design System 的层次架构

📊 四层模型
1
Design Token — 最底层原子:颜色、字号、间距、圆角、阴影、动画时长。这些是整个系统的单一事实来源(Single Source of Truth)。
2
基础样式 — Token 的组合:排版系统(h1-h6 + body + caption)、颜色系统(语义色:primary/success/warning/error)、间距系统(4/8/12/16/24/32/48px)。
3
组件 — 可复用的 UI 单元:Button、Input、Card、Modal、Toast。每个组件接受 Token 层的约束,暴露语义化的 API。
4
模式 — 组件的组合模式:表单布局(Label + Input + Error)、数据表格(Header + Row + Pagination + Filter)、设置页面(Tabs + Section + Toggle)。

Design Token 实战

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

Token 到 CSS 的管道

// 使用 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"
      }]
    }
  }
}
💡 关键原则:Token 一处定义,多处消费。Figma Variables → Style Dictionary → CSS Variables / Tailwind Config / iOS Swift / Android Kotlin。改一个 Token 值,全平台同步更新。

组件文档模式

每个组件都需要文档,最佳实践是使用 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>
  ),
}

SaaS Design System Checklist

✅ 必须包含

shadcn/ui — 组件即代码的新范式

是什么

shadcn/ui 不是传统的组件库——它不是一个 npm 包。它是一组可复制到项目中的高质量 React 组件代码,基于 Radix UI 原语 + Tailwind CSS + class-variance-authority 构建。你通过 CLI 拷贝组件到项目中,拥有完全控制权。

⚠️ 核心差异:传统组件库 = import { Button } from 'antd'(黑盒,升级受制于库版本)。shadcn/ui = 代码在你的项目中(白盒,你决定一切)。

为什么 shadcn/ui 赢了

🎯 零抽象税

没有额外的抽象层。组件代码就在你项目里,你可以直接修改任何细节——不需要 fork、不需要 override、不需要 !important

🎨 设计自由

Tailwind + CVA 让样式修改极其简单。想改圆角?改一行 class。想加动画?加一行 class。不需要和组件库的设计决策作斗争。

♿ 无障碍内置

基于 Radix UI 原语,键盘导航、ARIA、焦点管理全部内置。你不需要自己处理 Modal 的焦点陷阱或 Dropdown 的键盘交互。

📦 无依赖负担

只添加你用到的组件。不需要的组件不会出现在 bundle 中。没有 antd 那样的 2MB 全量包。

实战:用 shadcn/ui 构建组件

# 初始化
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 与其他组件库对比

特性 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 风格 快速原型

图标与图片资源

图标方案

🏆 Lucide Icons — 推荐默认选择

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> 组件自动处理:

  • WebP/AVIF 自动转换
  • 响应式 srcset
  • 懒加载 + blur 占位
  • CDN 集成(Vercel/S3)
🎨 插画资源
  • unDraw — 免费、可调品牌色
  • Humaaans — 可组合人物插画
  • Open Peeps — 手绘风格
  • Storyset — 动画插画

AI 辅助设计:v0 / Galileo / Midjourney

v0 — Vercel 的 AI UI 生成器

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>
  )
}
💡 AI 设计工具的正确用法:不是让 AI "设计你的产品",而是让 AI 生成初始结构,然后你用 Design System 约束它。v0 + shadcn/ui = 快速出原型 + 代码可维护。

AI 设计工具对比

工具 输入 输出 代码质量 适合
v0 文本/图片 React + shadcn 高(可生产用) 组件级原型
Galileo AI 文本 Figma 设计稿 中等 设计探索
Uizard 文本/草图 设计稿 + 代码 低-中 快速线框图
Midjourney 文本 图片 N/A(视觉参考) 品牌视觉/插画
DALL-E 文本 图片 N/A 图标/插画概念
Bolt.new 文本 完整 Web 应用 中等 MVP 验证

设计到开发的工作流

理想工作流

🔄 从想法到上线的设计流程
1
需求 → 线框图 — 用 Excalidraw / tldraw 画粗略线框。不追求美观,只验证布局和信息层次。5 分钟搞定,而不是 2 小时。
2
线框 → Figma 高保真 — 在 Figma 中用已有组件库拼装。关键:用 Auto Layout(= Flexbox),用 Variables(= Design Token),用 Components(= React 组件)。
3
Figma → 开发交付 — 开发者在 Dev Mode 中查看 CSS 值,对照 shadcn/ui 组件实现。有差异的地方记录在 Figma Comment 中。
4
代码实现 → 视觉验证 — 用 Chromatic / Percy 做视觉回归测试。每次 PR 自动截图对比,确保设计还原度。

小团队捷径:Code-First 设计

没有专职设计师?跳过 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>
  )
}
💡 Code-First 不等于无设计:你仍然需要 Design Token、间距系统、颜色系统。只是把 Figma 这一步省了,直接在代码中做设计决策。shadcn/ui 的组件就是你的"设计稿"。

视觉回归测试

// 使用 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

工具对比与选型

按团队阶段选型

👤 Solo 开发者(0 设计师)
推荐:Code-First 设计
├── Tailwind CSS + shadcn/ui(直接在代码中设计)
├── v0.dev(AI 生成初始布局)
├── Excalidraw(快速线框图)
├── Lucide Icons
└── 跳过 Figma
👥 小团队(1-2 设计师 + 2-5 开发)
推荐:Figma + Code 双轨
├── Figma(设计稿 + 组件库 + Dev Mode 交付)
├── Tailwind CSS + shadcn/ui(代码实现)
├── Figma Variables → Style Dictionary → Tailwind Config
├── Storybook(组件文档 + 视觉回归)
└── Chromatic(自动化视觉测试)
🏢 大团队(3+ 设计师 + 10+ 开发)
推荐:完整 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 免费基础版

常见坑与反模式

💀 坑 1:过早建设 Design System

产品还在探索期就花 2 个月建 Design System?大概率你的 Token 和组件会在 3 个月后全部推翻。

正确做法:先用 shadcn/ui + Tailwind 的默认 Token,等产品形态稳定后再抽取自己的 Design System。通常在 5-10 个页面之后你才能看清模式。

💀 坑 2:Figma 和代码脱节

设计师在 Figma 更新了按钮圆角,开发者的代码还是旧的?这是最常见的协作问题。

解决方案:

💀 坑 3:用 CSS-in-JS 的新项目

2026 年新项目还在用 styled-components?运行时 CSS-in-JS 已经被业界抛弃。

为什么:SSR 性能问题、bundle 体积、hydration 不匹配。Next.js 官方也不推荐。

替代:Tailwind(实用优先)或 Panda CSS(编译时 CSS-in-JS,类型安全)。

💀 坑 4:不测试暗色模式

90% 的开发者只在亮色模式下开发。结果暗色模式下对比度不足、图片没有适配、颜色硬编码。

规则:每个组件必须同时测试亮色和暗色。用 dark: 前缀,用 CSS 变量,不硬编码颜色值。

💀 坑 5:过度依赖 AI 生成

v0 生成的代码直接上线?你可能得到一个看起来不错但违反你 Design System 的组件。

正确做法:AI 生成 → 用你的 Design Token 替换硬编码值 → 用你的组件库替换重复组件 → 测试无障碍。

💀 坑 6:用 Ant Design 做 SaaS 前台

Ant Design 适合后台管理系统,但它的设计语言太重、定制太难、bundle 太大。SaaS 面向用户的产品需要更轻量、更现代的设计。

替代:shadcn/ui(轻量 + 可定制)或 Radix + Tailwind 自建。

💀 坑 7:忽视无障碍设计

很多团队把无障碍当成"后续优化",但 WCAG 2.1 AA 是很多地区的法律要求(包括欧盟的 European Accessibility Act 2025)。

最低标准:颜色对比度 4.5:1、键盘可导航、所有交互元素有 focus 样式、图片有 alt 文本。

📚 必读
🔗 相关模块
🛠️ 实用工具