设计规范 精选推荐

企业级设计系统构建指南:从 0 到 1 打造可扩展的组件库

HTMLPAGE 团队
20 分钟阅读

全面解析企业级设计系统的构建方法,包括设计原则制定、组件架构设计、Design Token 管理、文档体系建设、多主题支持等核心实践,帮助团队打造可持续演进的设计基础设施。

#设计系统 #组件库 #Design Token #UI 架构 #前端工程化

企业级设计系统构建指南

引言:为什么需要设计系统

"我们需要一个组件库"——这可能是很多团队开始构建设计系统的初衷。但设计系统远不止于一堆可复用的组件。它是产品体验一致性的保障,是设计与开发协作效率的倍增器,是品牌价值的技术载体。

这篇文章分享构建企业级设计系统的完整方法论,从战略规划到技术实现,从组件设计到生态建设。

第一部分:战略规划

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-disabledaria-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 }}

结语:设计系统是一段旅程

构建设计系统不是一次性的项目,而是一段持续的旅程。它需要设计师和开发者的紧密协作,需要产品团队的认可和采用,需要持续的投入和迭代。

成功的设计系统不是最完美的系统,而是真正被使用的系统。从小处着手,解决实际问题,逐步演进,这比追求一步到位的完美方案更加务实。

希望这篇指南能为你构建设计系统提供一些实用的参考和启发。


参考资源