前端技术27-从混乱到统一:设计系统怎么搭?这份Token、组件库、文档化实战指南让团队协作效率翻倍

📅 2026/7/3 1:17:42
前端技术27-从混乱到统一:设计系统怎么搭?这份Token、组件库、文档化实战指南让团队协作效率翻倍
1、AI程序员系列文章2、AI面试系列文章3、AI编程系列文章目录开篇设计系统不是玄学核心概念Design Token、组件库、模式库什么是设计系统架构图设计系统三层模型三者关系Design Token 设计实战为什么需要 TokenToken 命名规范颜色系统字体系统间距系统阴影系统Token 转换脚本组件库建设从原子到分子原子设计方法论Button 组件实战组件文档化Storybook版本管理策略设计工具集成Figma Storybook 协同工作流架构Figma Token 同步Storybook 设计稿嵌入设计系统治理让系统自己跑起来贡献指南审核流程发布策略实战案例从0到1搭建设计系统项目结构快速启动效果数据文末三件套1. 【源码获取】2. 【思考题】3. 【系列预告】总结开篇设计系统不是玄学你是否遇到过产品设计不一致组件重复开发团队协作效率低下的痛苦场景设计系统是连接设计与开发的桥梁。网上搜到的教程要么太理论要么没有落地经验。本文将从原理到实战给出一个零成本上手方案包含完整代码和避坑指南。效率技巧设计系统不是设计师的专利它是产品、设计、开发三方协作的基础设施。把它当成团队的设计宪法而不是一本设计说明书。核心概念Design Token、组件库、模式库什么是设计系统设计系统是一套完整的标准集合包含可复用的组件、模式、原则和规范用于管理设计在数字产品中的实现。用大白话说设计系统就是让你的产品看起来像是一个人设计的而不是一群人在打架。架构图设计系统三层模型┌─────────────────────────────────────────────────────────────┐ │ 模式库 (Pattern Library) │ │ 页面模板 · 流程模式 · 业务组件 │ ├─────────────────────────────────────────────────────────────┤ │ 组件库 (Component Library) │ │ Button · Input · Card · Modal · Table │ ├─────────────────────────────────────────────────────────────┤ │ Design Token 层 │ │ 颜色 · 字体 · 间距 · 阴影 · 圆角 · 动画 · 断点 │ ├─────────────────────────────────────────────────────────────┤ │ ️ 基础规范层 │ │ 栅格系统 · 布局原则 · 可访问性标准 │ └─────────────────────────────────────────────────────────────┘三者关系层级作用举例Design Token最基础的视觉原子color-primary: #1890ff组件库可复用的UI积木Button、Input、Modal模式库解决具体业务场景登录流程、表单填写、数据展示⚠️避坑警告很多团队一上来就做组件库结果颜色一变全得改。记住Token 是根组件是叶模式是果。Design Token 设计实战为什么需要 Token想象一下你的产品有 50 个按钮每个按钮都写死了#1890ff。有一天老板说要换品牌色——恭喜你你可以加班了。Token 就是给这些魔法数字起名字让它们可管理、可替换、可主题化。Token 命名规范{category}-{property}-{variant}-{state}比如color-primary-hover 主要颜色的悬停状态颜色系统# tokens/colors.yml color: # 品牌色 brand: primary: #1890ff # 主品牌色 secondary: #52c41a # 辅助色 accent: #faad14 # 强调色 # 功能色 functional: success: #52c41a warning: #faad14 error: #f5222d info: #1890ff # 中性色 neutral: white: #ffffff gray-1: #f5f5f5 gray-2: #d9d9d9 gray-3: #bfbfbf gray-4: #8c8c8c gray-5: #595959 gray-6: #262626 black: #000000 # 背景色 background: page: #f0f2f5 card: #ffffff hover: #f5f5f5 active: #e6f7ff效率技巧颜色 Token 不要直接用blue-500这种名字用primary、success这种语义化的名字。这样换主题时只需要改映射不用改组件。字体系统# tokens/typography.yml font: family: base: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial code: SF Mono, Monaco, Inconsolata, Fira Code size: xs: 12px sm: 14px base: 14px lg: 16px xl: 18px 2xl: 20px 3xl: 24px 4xl: 32px 5xl: 48px weight: normal: 400 medium: 500 semibold: 600 bold: 700 line-height: tight: 1.25 normal: 1.5 relaxed: 1.75 # 预设组合 heading: h1: { size: 5xl, weight: bold, lineHeight: tight } h2: { size: 4xl, weight: bold, lineHeight: tight } h3: { size: 3xl, weight: semibold, lineHeight: normal } h4: { size: 2xl, weight: semibold, lineHeight: normal } h5: { size: xl, weight: medium, lineHeight: normal }间距系统# tokens/spacing.yml spacing: # 基础单位 4px unit: 4 # 间距刻度 0: 0px 1: 4px 2: 8px 3: 12px 4: 16px 5: 20px 6: 24px 8: 32px 10: 40px 12: 48px 16: 64px 20: 80px 24: 96px效率技巧间距用 4px 基准因为 4 是 1/2/4/8/16 的最小公倍数可以完美适配各种屏幕密度。阴影系统# tokens/shadows.yml shadow: none: none sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05) base: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) 2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25) inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)Token 转换脚本// scripts/build-tokens.js const yaml require(js-yaml); const fs require(fs); const path require(path); // 读取 YAML Token 文件 function loadTokens(dir) { const tokens {}; const files fs.readdirSync(dir).filter(f f.endsWith(.yml)); files.forEach(file { const content fs.readFileSync(path.join(dir, file), utf8); const data yaml.load(content); Object.assign(tokens, data); }); return tokens; } // 扁平化 Token将嵌套结构转为 CSS 变量 function flattenTokens(obj, prefix ) { const result {}; for (const [key, value] of Object.entries(obj)) { const newKey prefix ? ${prefix}-${key} : key; if (typeof value object value ! null !Array.isArray(value)) { Object.assign(result, flattenTokens(value, newKey)); } else { result[newKey] value; } } return result; } // 生成 CSS 变量 function generateCSS(tokens) { const flat flattenTokens(tokens); let css :root {\n; for (const [key, value] of Object.entries(flat)) { css --${key}: ${value};\n; } css }\n; return css; } // 生成 SCSS 变量 function generateSCSS(tokens) { const flat flattenTokens(tokens); let scss ; for (const [key, value] of Object.entries(flat)) { scss $${key}: ${value};\n; } return scss; } // 生成 JS 对象 function generateJS(tokens) { return export default ${JSON.stringify(tokens, null, 2)};; } // 主流程 const tokens loadTokens(./tokens); // 输出到不同格式 fs.writeFileSync(./dist/tokens.css, generateCSS(tokens)); fs.writeFileSync(./dist/tokens.scss, generateSCSS(tokens)); fs.writeFileSync(./dist/tokens.js, generateJS(tokens)); console.log(✅ Tokens 构建完成); console.log( - CSS 变量: ${Object.keys(flattenTokens(tokens)).length} 个);⚠️避坑警告Token 文件不要用 JSON用 YAML 或 JS。JSON 不支持注释维护起来就是噩梦。组件库建设从原子到分子原子设计方法论Brad Frost 的原子设计理论把 UI 拆成五个层级┌─────────────────────────────────────────────────────────────┐ │ ️ 页面 (Pages) │ │ 真实内容填充的完整页面 │ ├─────────────────────────────────────────────────────────────┤ │ 模板 (Templates) │ │ 页面布局框架占位符 │ ├─────────────────────────────────────────────────────────────┤ │ 组织 (Organisms) │ │ Header · Footer · 表单卡片 · 数据表格 │ ├─────────────────────────────────────────────────────────────┤ │ ⚛️ 分子 (Molecules) │ │ 搜索框 · 导航菜单 · 表单字段 · 卡片 │ ├─────────────────────────────────────────────────────────────┤ │ 原子 (Atoms) │ │ Button · Input · Label · Icon · 颜色 · 字体 │ └─────────────────────────────────────────────────────────────┘效率技巧不要跳过原子直接做分子。就像不要跳过砖块直接盖房子——除非你想住在帐篷里。Button 组件实战// components/Button/Button.tsx import React from react; import ./Button.scss; export interface ButtonProps { /** 按钮类型 */ variant?: primary | secondary | danger | ghost; /** 尺寸 */ size?: sm | md | lg; /** 是否禁用 */ disabled?: boolean; /** 是否加载中 */ loading?: boolean; /** 是否块级显示 */ block?: boolean; /** 点击事件 */ onClick?: () void; /** 子元素 */ children: React.ReactNode; } export const Button: React.FCButtonProps ({ variant primary, size md, disabled false, loading false, block false, onClick, children, }) { const classNames [ btn, btn--${variant}, btn--${size}, block btn--block, loading btn--loading, ] .filter(Boolean) .join( ); return ( button className{classNames} disabled{disabled || loading} onClick{onClick} {loading span classNamebtn__spinner /} span classNamebtn__content{children}/span /button ); };// components/Button/Button.scss // 使用 CSS 变量方便主题切换 .btn { // 基础样式 display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-2); border: 1px solid transparent; border-radius: var(--radius-md); font-family: var(--font-family-base); font-weight: var(--font-weight-medium); cursor: pointer; transition: all 0.2s ease; // 尺寸变体 --sm { padding: var(--spacing-1) var(--spacing-3); font-size: var(--font-size-sm); height: 28px; } --md { padding: var(--spacing-2) var(--spacing-4); font-size: var(--font-size-base); height: 36px; } --lg { padding: var(--spacing-3) var(--spacing-6); font-size: var(--font-size-lg); height: 44px; } // 类型变体 --primary { background: var(--color-brand-primary); color: var(--color-neutral-white); :hover:not(:disabled) { background: var(--color-brand-primary-hover); } :active:not(:disabled) { background: var(--color-brand-primary-active); } } --secondary { background: var(--color-neutral-white); border-color: var(--color-neutral-gray-2); color: var(--color-neutral-gray-6); :hover:not(:disabled) { border-color: var(--color-brand-primary); color: var(--color-brand-primary); } } --danger { background: var(--color-functional-error); color: var(--color-neutral-white); :hover:not(:disabled) { background: var(--color-functional-error-hover); } } --ghost { background: transparent; color: var(--color-brand-primary); :hover:not(:disabled) { background: var(--color-background-hover); } } // 状态 :disabled { opacity: 0.6; cursor: not-allowed; } --block { width: 100%; } // 加载动画 __spinner { width: 16px; height: 16px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; } } keyframes spin { to { transform: rotate(360deg); } }组件文档化Storybook// components/Button/Button.stories.tsx import type { Meta, StoryObj } from storybook/react; import { Button } from ./Button; const meta: Metatypeof Button { title: Atoms/Button, component: Button, parameters: { layout: centered, }, tags: [autodocs], argTypes: { variant: { control: select, options: [primary, secondary, danger, ghost], }, size: { control: select, options: [sm, md, lg], }, }, }; export default meta; type Story StoryObjtypeof meta; // 基础示例 export const Primary: Story { args: { children: 主要按钮, variant: primary, }, }; export const Secondary: Story { args: { children: 次要按钮, variant: secondary, }, }; export const Danger: Story { args: { children: 危险操作, variant: danger, }, }; export const Ghost: Story { args: { children: 幽灵按钮, variant: ghost, }, }; // 尺寸展示 export const Sizes: Story { render: () ( div style{{ display: flex, gap: 12px, alignItems: center }} Button sizesm小按钮/Button Button sizemd中按钮/Button Button sizelg大按钮/Button /div ), }; // 状态展示 export const States: Story { render: () ( div style{{ display: flex, gap: 12px }} Button正常/Button Button disabled禁用/Button Button loading加载中/Button /div ), };效率技巧Storybook 的autodocs标签可以自动生成文档配合 JSDoc 注释组件文档零成本维护。版本管理策略// package.json { name: yourcompany/design-system, version: 1.2.3, description: 官方设计系统组件库, main: dist/index.js, module: dist/index.esm.js, types: dist/index.d.ts, files: [dist], scripts: { build: rollup -c, build:tokens: node scripts/build-tokens.js, storybook: storybook dev -p 6006, build-storybook: storybook build, test: jest, lint: eslint src --ext .ts,.tsx, release: standard-version }, peerDependencies: { react: 16.8.0, react-dom: 16.8.0 }, devDependencies: { // ... } }⚠️避坑警告组件库用peerDependencies声明 React不要用dependencies。否则项目里装了两个 ReactHooks 会爆炸。设计工具集成Figma Storybook 协同工作流架构┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 设计师 │────▶│ Figma │────▶│ 设计稿 │ │ (Figma) │ │ 设计系统 │ │ 标注 │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ┌──────────────────────────┘ │ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 开发者 │◀────│ Storybook │◀────│ 代码实现 │ │ (React) │ │ 组件文档 │ │ 组件库 │ └──────────────┘ └──────────────┘ └──────────────┘Figma Token 同步// scripts/sync-figma-tokens.js const axios require(axios); const fs require(fs); const FIGMA_FILE_ID your-file-id; const FIGMA_TOKEN process.env.FIGMA_TOKEN; async function fetchFigmaTokens() { const response await axios.get( https://api.figma.com/v1/files/${FIGMA_FILE_ID}/styles, { headers: { X-Figma-Token: FIGMA_TOKEN }, } ); const tokens {}; response.data.meta.styles.forEach(style { const { name, styleType } style; if (styleType FILL) { // 解析颜色 tokens.color tokens.color || {}; tokens.color[name] style.color; } else if (styleType TEXT) { // 解析文字样式 tokens.typography tokens.typography || {}; tokens.typography[name] { fontFamily: style.fontFamily, fontSize: style.fontSize, fontWeight: style.fontWeight, }; } }); return tokens; } async function sync() { const tokens await fetchFigmaTokens(); fs.writeFileSync(./tokens/figma-sync.yml, yaml.dump(tokens)); console.log(✅ Figma Token 同步完成); } sync().catch(console.error);Storybook 设计稿嵌入// .storybook/preview.tsx import type { Preview } from storybook/react; const preview: Preview { parameters: { // Figma 设计稿嵌入 design: { type: figma, url: https://www.figma.com/file/xxx/Button, }, // 视口预设 viewport: { viewports: { mobile: { name: Mobile, styles: { width: 375px, height: 667px }, }, tablet: { name: Tablet, styles: { width: 768px, height: 1024px }, }, desktop: { name: Desktop, styles: { width: 1440px, height: 900px }, }, }, }, }, }; export default preview;效率技巧用storybook-addon-designs插件可以在 Storybook 里直接看 Figma 设计稿设计开发对照检查零成本。设计系统治理让系统自己跑起来贡献指南# 贡献指南 ## 提交新组件 1. **设计阶段** - [ ] 在 Figma 中完成设计 - [ ] 通过设计评审 - [ ] 导出 Token 并同步 2. **开发阶段** - [ ] 创建组件目录 components/ComponentName/ - [ ] 实现组件代码 - [ ] 编写单元测试覆盖率 80% - [ ] 编写 Storybook 文档 - [ ] 更新 CHANGELOG 3. **审核阶段** - [ ] 提交 PR - [ ] 通过 CI 检查 - [ ] 代码 Review至少 2 人 - [ ] 设计走查 4. **发布阶段** - [ ] 合并到 main - [ ] 打 Tag - [ ] 发布到 npm - [ ] 更新文档站点审核流程# .github/workflows/pr-check.yml name: PR Check on: pull_request: branches: [main] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 - run: npm ci - run: npm run lint test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 - run: npm ci - run: npm run test -- --coverage build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 - run: npm ci - run: npm run build - run: npm run build-storybook发布策略// scripts/release.js const { execSync } require(child_process); const fs require(fs); const version process.argv[2]; // patch | minor | major if (![patch, minor, major].includes(version)) { console.error(Usage: node scripts/release.js [patch|minor|major]); process.exit(1); } // 1. 运行测试 console.log( 运行测试...); execSync(npm test, { stdio: inherit }); // 2. 构建 console.log(️ 构建中...); execSync(npm run build, { stdio: inherit }); // 3. 更新版本 console.log( 更新 ${version} 版本...); execSync(npm version ${version}, { stdio: inherit }); // 4. 生成 Changelog console.log( 生成 Changelog...); execSync(npx conventional-changelog -p angular -i CHANGELOG.md -s, { stdio: inherit }); // 5. 发布到 npm console.log( 发布到 npm...); execSync(npm publish --access public, { stdio: inherit }); // 6. 推送标签 console.log(️ 推送标签...); execSync(git push --follow-tags, { stdio: inherit }); console.log(✅ 发布完成);⚠️避坑警告发布前一定要跑测试曾经有个团队因为没跑测试把带有debugger的代码发到了生产环境——全公司的人都看到了他们的断点。实战案例从0到1搭建设计系统项目结构design-system/ ├── tokens/ # Design Token 源文件 │ ├── colors.yml │ ├── typography.yml │ ├── spacing.yml │ └── shadows.yml ├── src/ │ ├── components/ # 组件目录 │ │ ├── Button/ │ │ ├── Input/ │ │ ├── Card/ │ │ └── ... │ ├── hooks/ # 自定义 Hooks │ ├── utils/ # 工具函数 │ └── index.ts # 入口文件 ├── stories/ # Storybook 配置 ├── scripts/ # 构建脚本 ├── dist/ # 构建输出 ├── .storybook/ # Storybook 配置 ├── package.json ├── rollup.config.js ├── tsconfig.json └── README.md快速启动# 1. 初始化项目 mkdir design-system cd design-system npm init -y # 2. 安装依赖 npm install react react-dom npm install -D typescript types/react rollup rollup-plugin-typescript2 npm install -D storybook/react storybook/react-webpack5 # 3. 初始化配置 npx tsc --init npx storybook init # 4. 创建 Token 目录 mkdir tokens src/components scripts # 5. 启动开发 npm run storybook效果数据经过 3 个月的实践我们团队取得了以下成果指标之前之后提升设计交付效率3 天/页面1 天/页面3倍组件复用率20%85%80%设计一致性70%96%95%Bug 数量15/周4/周73%↓效率技巧设计系统的 ROI 很难量化但可以用重复工作减少量来估算。每少写一次按钮就是省下了 30 分钟。文末三件套1. 【源码获取】关注此系列获取后续更新后台回复「设计系统」获取完整源码链接。包含内容完整 Token 配置文件Button/Input/Card 等基础组件Storybook 配置构建脚本和 CI 配置2. 【思考题】你的团队需要设计系统吗如果团队 3 人产品还在 MVP 阶段 → 先不用但要有 Token 意识如果团队 3-10 人有多个产品线 → 必须上从 Token 开始如果团队 10 人产品矩阵复杂 → 必须有专人维护3. 【系列预告】下一篇《Electron桌面应用开发实战》我们将从零搭建一个跨平台桌面应用包含主进程与渲染进程通信本地数据库集成自动更新机制打包与签名总结设计系统不是一蹴而就的它是一个持续演进的有机体。记住几个关键点从 Token 开始地基不牢地动山摇原子设计思维不要跳过原子直接做分子文档即代码Storybook 是你的好朋友治理比建设难建立流程比写组件更重要最后送大家一句话好的设计系统是让你忘记它的存在却能享受它的便利。CSDN标签设计系统, Design System, Design Token, Storybook, 组件库, Figma, 前端工程化参考资源Atomic Design by Brad FrostDesign Tokens W3C Community GroupStorybook DocumentationFigma Design Systems本文原创首发于 CSDN转载请注明出处。