MODO®
ISSUE 07 / 2026
03 工程手记 / Engineering

设计系统
搭建实战

从 Token 到组件库到文档,一次小型设计系统的完整落地。三个月、两个产品线、四十多个组件,最终把"改个按钮要过五个 PR"的混乱收束成可复用的资产。

作者 林知远
日期 2026.06.10
时长 15 分钟
标签 设计系统 · 前端

项目有两个产品线,五六十个页面,UI 散落在六个仓库里。改一次品牌色要在二十多处手改,按钮的圆角值有三个版本同时在线。我们决定建一套设计系统,把「散点」收束成「单源」。

三个月后,Token 单源、组件库统一、文档可交互。这篇文章记录完整的搭建路径——从地基到屋顶。

01

从 Token 开始:设计系统的地基

设计系统最容易跑偏的地方是——一上来就画组件。但组件的稳定性取决于它依赖的值是否单源。Token 就是那个单源。

三层 Token 结构

我们把 Token 分成三层,每层职责不同:

层 命名 职责 示例
基础层 color-red-500 原始值,不参与业务 #ff3d00
语义层 color-accent 赋予含义,可换源 = 基础层 red-500
组件层 button-bg-primary 绑定到具体组件 = 语义层 accent
// tokens/color.ts —— 基础层
export const red = {
  500: '#ff3d00',
  600: '#e63600',
} as const;

// tokens/semantic.ts —— 语义层
export const semantic = {
  'color-accent': red[500],
  'color-danger': red[600],
  'color-ink':   '#0d0d0d',
} as const;

// tokens/component.ts —— 组件层
export const component = {
  'button-bg-primary':   semantic['color-accent'],
  'button-text-primary': '#ffffff',
  'input-border':        semantic['color-ink'],
} as const;

三层的好处是——换品牌色只改一处。把语义层的 color-accent 指向新的基础值,所有组件自动跟随,因为它们只认语义层、不认基础层。

NOTE

组件只消费语义层和组件层,禁止直接引用基础层。这条 lint 规则是 Token 体系不腐化的关键。一旦有组件写了 red[500],换色就要改它。

Token 的多平台分发

同一个 Token 源,要分发给 CSS 变量、Tailwind 配置、iOS / Android 平台。用 Style Dictionary 做转换:

// style-dictionary.config.json
{
  "source": ["tokens/**/*.ts"],
  "platforms": {
    "css": {
      "transformGroup": "css",
      "buildPath": "dist/css/",
      "files": [{ "destination": "tokens.css", "format": "css/variables" }]
    },
    "js": {
      "transformGroup": "js",
      "buildPath": "dist/js/",
      "files": [{ "destination": "tokens.js", "format": "javascript/es6" }]
    }
  }
}

一次定义、多处分发,是设计系统作为「工程产物」而非「Figma 文档」的分水岭。

02

组件库:分层而非堆叠

组件库不是把所有 UI 往一个目录里堆。我们按「原子 → 分子 → 有机体 → 模板」的原子设计思路分层,每层有明确的边界:

层 组件 知道什么 不该知道什么
原子 Button、Icon、Input 自己的样式、a11y 业务语义
分子 FormField、SearchBar 几个原子的组合 数据获取
有机体 DataTable、FormSection 组合 + 交互 路由、全局态
模板 PageLayout 页面骨架 具体业务内容

关键是禁止越层引用:原子不该 import 有机体。一旦下层依赖上层,整个分层就退化为网状依赖,谁也拆不出来。

API 设计:可控与可组合

// 组件 props 的三条铁律:
// 1. 职责单一:Button 只负责"点击"
// 2. 透传原生属性:不发明平行 API
// 3. 变体用对象,不用布尔堆叠

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: 'primary' | 'ghost' | 'danger';
  size?: 'sm' | 'md' | 'lg';
};

// ❌ variant 多了就退化成布尔地狱
// <Button primary danger outline />  ← 语义冲突

// ✅ 变体约束互斥
<Button variant="primary" size="md" />

组件的可复用性,取决于它对外部世界的无知程度。

— 组件 API 审查原则
03

文档:让规范可执行

没有可交互的文档,设计系统等于没有。我们用 Storybook 做了一件事——让每个组件的所有状态都可被看见。

覆盖所有状态而非只画默认态

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Atoms/Button',
  component: Button,
  // 穷举所有合法组合,作为视觉回归基线
  args: { children: '按钮' },
};
export default meta;

export const Primary: StoryObj = {
  args: { variant: 'primary', size: 'md' },
};
export const PrimaryDisabled: StoryObj = {
  args: { variant: 'primary', size: 'md', disabled: true },
};
export const Danger: StoryObj = {
  args: { variant: 'danger', size: 'md' },
};

这一步最大的价值不是"展示",而是给视觉回归测试留基线。配合 Chromatic 做 snapshot,任何组件的视觉偏移都会在 CI 里被拦下——一致性从此由机器守。

文档即契约

每个组件的 Story 页面附带一段「何时用 / 何时别用」的说明。这段文字不是装饰,而是给 PR 评审当判据。当有人想用 Button 做 Tab 切换,评审可以指着文档说"这不符合它的设计意图"。

TIP

把"什么时候不该用"写进文档。反例比正例更能阻止误用。一份只写"怎么用"的文档,挡不住"我就借它一用"的诱惑。

04

治理:谁来把关一致性

设计系统不是交付物,是持续运转的机制。没有治理流程,三个月后它会重新散开。

三方协作模型

我们把治理拆成三个角色,互相制衡:

流程是:消费方提 RFC → 设计组评审意图 → 前端组评审实现 → 合并进主仓库 → 发版同步。任何一方跳过,变更都可能制造不一致。

版本化与变更日志

# 组件库独立发版,消费方按 semver 升级
npm version minor     # 新增组件 → minor
npm version patch     # 修复 → patch
npm version major     # 破坏性 API 变更 → major + 迁移指南

# CHANGELOG 由 conventional commits 自动生成
# 消费方升级时能看到每一个 breaking change 的迁移路径

独立版本化的真正意义是——把"改一个组件"和"所有产品同步"解耦。没有版本边界,一次"优化"就会变成对所有消费方的强迫升级。

设计系统的敌人不是复杂度,是熵增。

— 治理流程的设计起点
05

复盘与收益

三个月落地,六个月后回头看,量化收益如下:

20→1
品牌色修改点
3
按钮圆角版本收敛
47
可复用组件数
指标 建系统前 建系统后
新页面开发周期 5–8 天 2–3 天
UI 不一致工单 / 月 14 2
品牌色全量替换 2 人天 10 分钟

但更值钱的是非量化收益——产品、设计、前端第一次有了一份共享的词汇表。讨论从"按钮再灰一点"变成了"调 color-ink-3 的语义指向",争论的颗粒度变细,效率自然上来。

回头看走过的路,有四条经验值得留给后来者:

设计系统不是交付一个仓库,而是建立一种让一致性可持续的机制。代码可以重写,机制会沉淀下来——这才是它真正的价值。

设计系统 Design Token 组件库 落地复盘