企业级设计系统构建指南
引言:为什么需要设计系统
"我们需要一个组件库"——这可能是很多团队开始构建设计系统的初衷。但设计系统远不止于一堆可复用的组件。它是产品体验一致性的保障,是设计与开发协作效率的倍增器,是品牌价值的技术载体。
这篇文章分享构建企业级设计系统的完整方法论,从战略规划到技术实现,从组件设计到生态建设。
第一部分:战略规划
1.1 明确设计系统的定位
┌────────────────────────────────────────────────────────────┐
│ 设计系统的三层价值 │
├────────────────────────────────────────────────────────────┤
│ │
│ 战略层:品牌一致性 │
│ ├── 统一的视觉语言 │
│ ├── 一致的交互模式 │
│ └── 可预期的用户体验 │
│ │
│ 效率层:开发提速 │
│ ├── 减少重复开发 │
│ ├── 降低沟通成本 │
│ └── 加速产品迭代 │
│ │
│ 质量层:标准保障 │
│ ├── 可访问性合规 │
│ ├── 性能基准保证 │
│ └── 代码质量标准 │
│ │
└────────────────────────────────────────────────────────────┘
1.2 评估团队现状
## 设计系统成熟度评估
### Level 1:无系统
- 组件各自为战
- 样式硬编码
- 无复用机制
### Level 2:样式库
- 统一色彩、字体
- CSS 变量/工具类
- 基础规范文档
### Level 3:组件库
- 封装通用组件
- Props API 规范
- 使用文档
### Level 4:设计系统
- Design Token 体系
- 设计-开发协同流程
- 完整文档与示例
### Level 5:设计平台
- 多主题/多品牌支持
- 设计工具集成
- 自动化工作流
- 社区生态
1.3 制定演进路线图
## 设计系统建设路线图示例
### Phase 1:基础建设(Q1-Q2)
- [ ] 设计原则确立
- [ ] Design Token 定义
- [ ] 基础组件(10-15个)
- [ ] 开发环境搭建
### Phase 2:核心扩展(Q3)
- [ ] 复合组件(20-30个)
- [ ] 文档网站上线
- [ ] Figma 组件库同步
- [ ] 首个产品接入
### Phase 3:生态完善(Q4)
- [ ] 主题定制能力
- [ ] CLI 工具
- [ ] VS Code 插件
- [ ] 3个以上产品接入
### Phase 4:持续演进(次年)
- [ ] 多框架支持
- [ ] 设计工具链集成
- [ ] 社区运营
- [ ] 数据驱动优化
第二部分:设计原则
2.1 建立设计语言
## 设计原则示例
### 1. 清晰(Clarity)
用户应该能够立即理解界面的目的和操作方式。
- 使用熟悉的模式和约定
- 提供清晰的视觉层次
- 保持界面元素的一致性
### 2. 高效(Efficiency)
减少用户完成任务所需的步骤和时间。
- 简化复杂流程
- 提供智能默认值
- 支持键盘快捷键
### 3. 包容(Inclusive)
为所有用户提供无障碍的使用体验。
- 符合 WCAG 2.1 AA 标准
- 支持多种输入方式
- 考虑不同设备和网络环境
### 4. 愉悦(Delight)
在满足功能需求的同时创造积极的情感体验。
- 恰当的微交互反馈
- 有温度的文案设计
- 细节处的惊喜
2.2 定义设计约束
// 设计约束的代码化表达
// spacing.ts - 间距系统
export const spacing = {
0: '0',
1: '4px', // 0.25rem
2: '8px', // 0.5rem
3: '12px', // 0.75rem
4: '16px', // 1rem
5: '20px', // 1.25rem
6: '24px', // 1.5rem
8: '32px', // 2rem
10: '40px', // 2.5rem
12: '48px', // 3rem
16: '64px', // 4rem
} as const;
// 使用约束
// ❌ 随意的数值
<div style={{ padding: '13px' }} />
// ✅ 使用系统值
<div style={{ padding: spacing[3] }} />
// typography.ts - 字体系统
export const typography = {
fontFamily: {
sans: 'Inter, -apple-system, sans-serif',
mono: 'JetBrains Mono, monospace',
},
fontSize: {
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px',
'3xl': '30px',
'4xl': '36px',
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
},
lineHeight: {
tight: 1.25,
normal: 1.5,
relaxed: 1.75,
},
} as const;
第三部分:Design Token 体系
3.1 Token 的层级结构
┌────────────────────────────────────────────────────────────┐
│ Design Token 层级 │
├────────────────────────────────────────────────────────────┤
│ │
│ Reference Tokens(原始值) │
│ ├── color.blue.500: #3B82F6 │
│ ├── color.gray.100: #F3F4F6 │
│ └── space.4: 16px │
│ │
│ ↓ 语义映射 │
│ │
│ System Tokens(语义值) │
│ ├── color.primary: {color.blue.500} │
│ ├── color.background.primary: {color.gray.100} │
│ └── space.component.padding: {space.4} │
│ │
│ ↓ 组件应用 │
│ │
│ Component Tokens(组件值) │
│ ├── button.background: {color.primary} │
│ ├── card.background: {color.background.primary} │
│ └── card.padding: {space.component.padding} │
│ │
└────────────────────────────────────────────────────────────┘
3.2 Token 定义与管理
// tokens/color.json
{
"color": {
"primitive": {
"blue": {
"50": { "value": "#EFF6FF" },
"100": { "value": "#DBEAFE" },
"500": { "value": "#3B82F6" },
"600": { "value": "#2563EB" },
"700": { "value": "#1D4ED8" }
},
"gray": {
"50": { "value": "#F9FAFB" },
"100": { "value": "#F3F4F6" },
"200": { "value": "#E5E7EB" },
"500": { "value": "#6B7280" },
"900": { "value": "#111827" }
}
},
"semantic": {
"primary": {
"value": "{color.primitive.blue.500}",
"description": "主色调,用于主要操作和强调"
},
"primary-hover": {
"value": "{color.primitive.blue.600}"
},
"text": {
"primary": { "value": "{color.primitive.gray.900}" },
"secondary": { "value": "{color.primitive.gray.500}" }
},
"background": {
"primary": { "value": "#FFFFFF" },
"secondary": { "value": "{color.primitive.gray.50}" }
}
}
}
}
3.3 Token 转换与输出
// 使用 Style Dictionary 转换 Token
// config.js
module.exports = {
source: ['tokens/**/*.json'],
platforms: {
// CSS Variables
css: {
transformGroup: 'css',
buildPath: 'dist/css/',
files: [{
destination: 'variables.css',
format: 'css/variables',
options: {
outputReferences: true
}
}]
},
// TypeScript
ts: {
transformGroup: 'js',
buildPath: 'dist/ts/',
files: [{
destination: 'tokens.ts',
format: 'javascript/es6'
}]
},
// Tailwind
tailwind: {
transformGroup: 'js',
buildPath: 'dist/tailwind/',
files: [{
destination: 'tailwind.config.js',
format: 'tailwind/config'
}]
}
}
};
// 输出示例 - CSS Variables
:root {
--color-primary: #3B82F6;
--color-primary-hover: #2563EB;
--color-text-primary: #111827;
--color-background-primary: #FFFFFF;
}
// 输出示例 - TypeScript
export const color = {
primary: '#3B82F6',
primaryHover: '#2563EB',
text: {
primary: '#111827',
secondary: '#6B7280',
},
};
第四部分:组件架构设计
4.1 组件分类体系
┌────────────────────────────────────────────────────────────┐
│ 组件分类 │
├────────────────────────────────────────────────────────────┤
│ │
│ Primitives(原子组件) │
│ └── 最基础的构建块,无业务语义 │
│ Box, Text, Icon, Image, Spinner │
│ │
│ Elements(基础组件) │
│ └── 单一职责的 UI 元素 │
│ Button, Input, Select, Checkbox, Badge │
│ │
│ Patterns(模式组件) │
│ └── 组合多个元素的通用模式 │
│ FormField, SearchBar, Pagination, Tabs │
│ │
│ Templates(模板组件) │
│ └── 页面级别的布局模板 │
│ PageHeader, Sidebar, ContentArea, Modal │
│ │
│ Features(业务组件) │
│ └── 特定业务场景的组件(可选) │
│ UserCard, ProductList, CommentSection │
│ │
└────────────────────────────────────────────────────────────┘
4.2 组件 API 设计原则
// 1. 一致的命名约定
interface ButtonProps {
// 外观相关:variant, size, color
variant?: 'solid' | 'outline' | 'ghost' | 'link';
size?: 'sm' | 'md' | 'lg';
colorScheme?: 'primary' | 'secondary' | 'danger';
// 状态相关:is* 或 disabled/loading
isDisabled?: boolean;
isLoading?: boolean;
// 内容相关:语义化命名
leftIcon?: ReactNode;
rightIcon?: ReactNode;
// 事件:on* 前缀
onClick?: (event: MouseEvent) => void;
}
// 2. 合理的默认值
const Button = ({
variant = 'solid',
size = 'md',
colorScheme = 'primary',
...props
}: ButtonProps) => {
// ...
};
// 3. 支持组合(Composition)
<Button>
<ButtonIcon icon={<PlusIcon />} />
<ButtonText>添加</ButtonText>
</Button>
// 或更简洁的 API
<Button leftIcon={<PlusIcon />}>添加</Button>
// 4. 支持多态(Polymorphism)
// 可以渲染为不同的 HTML 元素
<Button as="a" href="/link">链接按钮</Button>
<Button as={Link} to="/route">路由按钮</Button>
4.3 组件实现示例
// components/Button/Button.tsx
import { forwardRef } from 'react';
import { clsx } from 'clsx';
import { Slot } from '@radix-ui/react-slot';
import { Spinner } from '../Spinner';
import styles from './Button.module.css';
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'solid' | 'outline' | 'ghost' | 'link';
size?: 'sm' | 'md' | 'lg';
colorScheme?: 'primary' | 'secondary' | 'danger';
isLoading?: boolean;
isDisabled?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
asChild?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
variant = 'solid',
size = 'md',
colorScheme = 'primary',
isLoading = false,
isDisabled = false,
leftIcon,
rightIcon,
asChild = false,
className,
children,
...props
},
ref
) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
ref={ref}
className={clsx(
styles.button,
styles[variant],
styles[size],
styles[colorScheme],
{
[styles.loading]: isLoading,
[styles.disabled]: isDisabled,
},
className
)}
disabled={isDisabled || isLoading}
{...props}
>
{isLoading && <Spinner className={styles.spinner} />}
{!isLoading && leftIcon && (
<span className={styles.leftIcon}>{leftIcon}</span>
)}
<span className={styles.text}>{children}</span>
{!isLoading && rightIcon && (
<span className={styles.rightIcon}>{rightIcon}</span>
)}
</Comp>
);
}
);
Button.displayName = 'Button';
/* components/Button/Button.module.css */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-weight: var(--font-weight-medium);
border-radius: var(--radius-md);
transition: all 0.2s ease;
cursor: pointer;
}
/* Variants */
.solid {
background: var(--color-primary);
color: white;
}
.solid:hover:not(.disabled) {
background: var(--color-primary-hover);
}
.outline {
background: transparent;
border: 1px solid var(--color-primary);
color: var(--color-primary);
}
/* Sizes */
.sm {
height: 32px;
padding: 0 var(--space-3);
font-size: var(--font-size-sm);
}
.md {
height: 40px;
padding: 0 var(--space-4);
font-size: var(--font-size-base);
}
.lg {
height: 48px;
padding: 0 var(--space-6);
font-size: var(--font-size-lg);
}
/* States */
.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.loading {
cursor: wait;
}
第五部分:主题系统
5.1 多主题架构
// theme/types.ts
export interface Theme {
name: string;
tokens: {
colors: ColorTokens;
spacing: SpacingTokens;
typography: TypographyTokens;
shadows: ShadowTokens;
radii: RadiusTokens;
};
}
// theme/themes/light.ts
export const lightTheme: Theme = {
name: 'light',
tokens: {
colors: {
background: {
primary: '#FFFFFF',
secondary: '#F9FAFB',
},
text: {
primary: '#111827',
secondary: '#6B7280',
},
// ...
},
// ...
},
};
// theme/themes/dark.ts
export const darkTheme: Theme = {
name: 'dark',
tokens: {
colors: {
background: {
primary: '#111827',
secondary: '#1F2937',
},
text: {
primary: '#F9FAFB',
secondary: '#9CA3AF',
},
// ...
},
// ...
},
};
5.2 主题 Provider 实现
// theme/ThemeProvider.tsx
import { createContext, useContext, useState, useEffect } from 'react';
import { lightTheme, darkTheme } from './themes';
import type { Theme } from './types';
interface ThemeContextValue {
theme: Theme;
setTheme: (theme: 'light' | 'dark' | 'system') => void;
resolvedTheme: 'light' | 'dark';
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [themeName, setThemeName] = useState<'light' | 'dark' | 'system'>('system');
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
useEffect(() => {
if (themeName === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
setResolvedTheme(mediaQuery.matches ? 'dark' : 'light');
const handler = (e: MediaQueryListEvent) => {
setResolvedTheme(e.matches ? 'dark' : 'light');
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
} else {
setResolvedTheme(themeName);
}
}, [themeName]);
// 应用主题变量到 DOM
useEffect(() => {
const theme = resolvedTheme === 'dark' ? darkTheme : lightTheme;
applyThemeToDOM(theme);
}, [resolvedTheme]);
const theme = resolvedTheme === 'dark' ? darkTheme : lightTheme;
return (
<ThemeContext.Provider value={{ theme, setTheme: setThemeName, resolvedTheme }}>
{children}
</ThemeContext.Provider>
);
}
function applyThemeToDOM(theme: Theme) {
const root = document.documentElement;
// 递归设置 CSS 变量
function setTokens(obj: Record<string, any>, prefix = '') {
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
setTokens(value, `${prefix}${key}-`);
} else {
root.style.setProperty(`--${prefix}${key}`, value);
}
}
}
setTokens(theme.tokens);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
第六部分:文档与治理
6.1 文档结构设计
docs/
├── getting-started/
│ ├── installation.md
│ ├── quick-start.md
│ └── migration.md
├── foundations/
│ ├── design-principles.md
│ ├── colors.md
│ ├── typography.md
│ ├── spacing.md
│ └── icons.md
├── components/
│ ├── overview.md
│ ├── button.mdx # 支持交互示例
│ ├── input.mdx
│ └── ...
├── patterns/
│ ├── forms.md
│ ├── navigation.md
│ └── data-display.md
├── recipes/
│ ├── authentication.md
│ ├── dashboard.md
│ └── settings.md
└── contributing/
├── architecture.md
├── testing.md
└── release.md
6.2 组件文档模板
// docs/components/button.mdx
import { Button } from '@design-system/react';
import { Playground, Props, Preview } from '@docs/components';
# Button
按钮用于触发操作或事件,如提交表单、打开对话框、取消操作等。
## 何时使用
- 需要触发即时操作时
- 提交或取消表单时
- 开始新的任务或流程时
## 基础用法
<Preview>
<Button>默认按钮</Button>
<Button variant="outline">线框按钮</Button>
<Button variant="ghost">幽灵按钮</Button>
</Preview>
```jsx
<Button>默认按钮</Button>
<Button variant="outline">线框按钮</Button>
<Button variant="ghost">幽灵按钮</Button>
尺寸
状态
交互演练
<Playground code={` <Button variant="solid" size="md" colorScheme="primary" onClick={() => alert('Clicked!')}
点击我 `} />
API
可访问性
- 默认支持键盘导航(Tab、Enter、Space)
- 自动管理
aria-disabled和aria-busy状态 - 加载状态时会宣告"加载中"给屏幕阅读器
设计指南
Do
- 使用清晰、动作导向的文案
- 保持按钮文案简洁(2-4个字)
- 主要操作使用 solid 变体
Don't
- 避免在一个视图中使用过多主按钮
- 不要使用模糊的文案如"确定"
- 不要将链接样式为按钮(语义问题)
### 6.3 版本管理与发布
```typescript
// package.json
{
"name": "@company/design-system",
"version": "2.1.0",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./css": "./dist/styles.css",
"./tokens": "./dist/tokens/index.js"
},
"scripts": {
"build": "rollup -c",
"test": "vitest",
"lint": "eslint src",
"release": "changeset publish"
}
}
// 使用 Changesets 管理版本
// .changeset/config.json
{
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch"
}
第七部分:工程化实践
7.1 Monorepo 结构
design-system/
├── packages/
│ ├── tokens/ # Design Tokens
│ │ ├── src/
│ │ └── package.json
│ ├── icons/ # 图标库
│ │ ├── src/
│ │ └── package.json
│ ├── react/ # React 组件
│ │ ├── src/
│ │ └── package.json
│ ├── vue/ # Vue 组件
│ │ ├── src/
│ │ └── package.json
│ └── docs/ # 文档网站
│ └── package.json
├── tools/
│ ├── eslint-config/
│ └── tsconfig/
├── pnpm-workspace.yaml
└── package.json
7.2 测试策略
// 组件单元测试
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('handles click events', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('shows loading state', () => {
render(<Button isLoading>Click me</Button>);
expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true');
});
it('is disabled when isDisabled is true', () => {
render(<Button isDisabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
// 可访问性测试
import { axe } from 'jest-axe';
it('has no accessibility violations', async () => {
const { container } = render(<Button>Accessible Button</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
// 视觉回归测试
// 使用 Chromatic 或 Percy
7.3 CI/CD 流程
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
- run: pnpm lint
- run: pnpm build
visual-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm chromatic --project-token=${{ secrets.CHROMATIC_TOKEN }}
release:
if: github.ref == 'refs/heads/main'
needs: [test, visual-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm build
- name: Create Release
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
结语:设计系统是一段旅程
构建设计系统不是一次性的项目,而是一段持续的旅程。它需要设计师和开发者的紧密协作,需要产品团队的认可和采用,需要持续的投入和迭代。
成功的设计系统不是最完美的系统,而是真正被使用的系统。从小处着手,解决实际问题,逐步演进,这比追求一步到位的完美方案更加务实。
希望这篇指南能为你构建设计系统提供一些实用的参考和启发。


