📋 目錄
同一個「按鈕」在 A 專案是綠色、在 B 專案是藍色?同一個 Modal 元件在三個地方有三種不同的實作?Design System 就是為了解決這個問題——讓整個團隊用同一套語言(Tokens)、同一套積木(元件)、同一套原則,建立一致的使用者體驗。
前言:Design System 是什麼?
很多人會把 Design System 和 Component Library(元件庫)搞混。簡單說:
- Component Library(元件事):一堆可復用的 UI 元件(Button、Modal、Dropdown)
- Design System(設計系統):更大的系統,包括設計語言(Tokens)、元件庫、开發原則、使用文件、甚至是設計師和工程師之間的協作流程
一個類比:
- Component Library 是一堆磚塊
- Design System 是蓋房子的完整方法論——包括要用什麼水泥、磚塊怎麼排列、什麼情況用什麼磚
為什麼前端工程師要關心 Design System? 因為當你的產品開始擴張、需要多個專案共享設計語言時,沒有 Design System 就會變成「到處都是看起來不太一樣的按鈕」的義大利麵。
Design System 的核心組成
1. Design Tokens(設計變數)
Design Tokens 是設計系統的原子層——所有視覺決策的變數。
// design-tokens.ts
export const tokens = {
// 色彩
color: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6', // 主要品牌色
900: '#1e3a8a',
},
neutral: {
50: '#fafafa',
500: '#737373',
900: '#171717',
},
success: '#22c55e',
danger: '#ef4444',
warning: '#f59e0b',
},
// 字體
font: {
family: {
sans: 'Inter, system-ui, sans-serif',
mono: 'JetBrains Mono, monospace',
},
size: {
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.5rem', // 24px
},
weight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
},
// 間距
spacing: {
1: '0.25rem', // 4px
2: '0.5rem', // 8px
3: '0.75rem', // 12px
4: '1rem', // 16px
6: '1.5rem', // 24px
8: '2rem', // 32px
},
// 圓角
radius: {
sm: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
full: '9999px',
},
} as const;
這些 Tokens 是設計師和工程師之間的橋樑——設計師在 Figma 裡用同樣的變數命名,工程師用 CSS 變數實作,雙方永遠同步。
2. 基礎元件(Primitive Components)
基礎元件是 UI 的最小單位——通常包裝原生 HTML 元素,加上 Design Tokens 的樣式:
// src/components/primitives/Button.tsx
import React from 'react';
import { tokens } from '../tokens';
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
isLoading?: boolean;
}
const variantStyles: Record<ButtonVariant, React.CSSProperties> = {
primary: { backgroundColor: tokens.color.primary[500], color: 'white' },
secondary: { backgroundColor: tokens.color.neutral[100], color: tokens.color.neutral[900] },
ghost: { backgroundColor: 'transparent', color: tokens.color.primary[500] },
danger: { backgroundColor: tokens.color.danger, color: 'white' },
};
const sizeStyles: Record<ButtonSize, React.CSSProperties> = {
sm: { padding: `${tokens.spacing[1]} ${tokens.spacing[3]}`, fontSize: tokens.font.size.sm },
md: { padding: `${tokens.spacing[2]} ${tokens.spacing[4]}`, fontSize: tokens.font.size.base },
lg: { padding: `${tokens.spacing[3]} ${tokens.spacing[6]}`, fontSize: tokens.font.size.lg },
};
export function Button({
variant = 'primary',
size = 'md',
isLoading = false,
children,
disabled,
style,
...props
}: ButtonProps) {
return (
<button
disabled={disabled || isLoading}
style={{
...variantStyles[variant],
...sizeStyles[size],
borderRadius: tokens.radius.md,
border: 'none',
cursor: disabled || isLoading ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.5 : 1,
fontWeight: tokens.font.weight.medium,
transition: 'all 0.15s ease',
...style,
}}
{...props}
>
{isLoading ? '載入中...' : children}
</button>
);
}
3. 複合元件(Composite Components)
複合元件由基礎元件組合而成,封裝更複雜的 UI 邏輯:
// src/components/composite/SearchInput.tsx
import React, { useState } from 'react';
import { tokens } from '../tokens';
import { Input } from '../primitives/Input';
import { Button } from '../primitives/Button';
interface SearchInputProps {
onSearch: (value: string) => void;
placeholder?: string;
}
export function SearchInput({ onSearch, placeholder = '搜尋...' }: SearchInputProps) {
const [value, setValue] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSearch(value);
};
return (
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: tokens.spacing[2] }}>
<Input
value={value}
onChange={setValue}
placeholder={placeholder}
style={{ flex: 1 }}
/>
<Button type="submit" variant="primary" size="md">
搜尋
</Button>
</form>
);
}
建立 Design System 的步驟
Step 1:盤點現有元件
在建立 Design System 之前,先把現有專案裡的元件盤點一次。問自己:
- 有多少種不同的 Button 樣式?
- 有多少種不同的 Colors、字體大小、間距?
- 哪些是重複的、可以合併的?
把重複的收集起來,是 Design System 的起點。
Step 2:定義 Design Tokens
從顏色、字體、間距、圓角這四個維度開始。先從最常用的定義,不需要一開始就定義完整的系統。
Step 3:建立基礎元件
從最常用的開始:Button、Input、Card、Badge。這些是整個系統的基礎。
Step 4:建立文件
用 Storybook 建立互動式文件,讓團隊可以即時預覽每個元件的不同變體。
使用 Storybook 建立文件
Storybook 是建立元件文件的標準工具:
npm init -y
npm install -D @storybook/react
npx storybook@latest init
// src/components/primitives/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Primitives/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost', 'danger'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
isLoading: { control: 'boolean' },
disabled: { control: 'boolean' },
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
variant: 'primary',
size: 'md',
children: '主要按鈕',
},
};
export const Secondary: Story = {
args: {
variant: 'secondary',
size: 'md',
children: '次要按鈕',
},
};
export const Loading: Story = {
args: {
variant: 'primary',
isLoading: true,
children: '載入中',
},
};
執行 npm run storybook,就可以在瀏覽器裡看到所有元件的互動式文件。
常見問題
Q:Design System 和 Component Library 哪個先做?
A:先做 Component Library,再逐步擴展成 Design System。不要一開始就想做一個完美的完整系統——從共享元件開始,看到價值了再逐步擴展。
Q:Design System 一定要用 Figma 嗎?
A:不一定,但設計師和工程師必須用同一套 Tokens。如果設計師在 Figma,工程師必須能從 Figma 匯出 Tokens(如使用 Token Studio)。Design System 的核心價值就是消除「設計和工程不一致」的問題。
Q:只有我一個人,有必要建立 Design System 嗎?
A:如果只有一個專案,不需要刻意建立 Design System。但可以把常用的元件抽出來做成 internal package,累積到 3-5 個專案需要共享時,再正規化。Design System 的協作價值在多人團隊才明顯。
Q:要用什麼工具建立 Design System?
A:常用組合:
- Tokens + Components:TypeScript + CSS-in-JS 或 Vanilla Extract
- 文件:Storybook(最流行)或 Ladle(更快、更簡單)
- 發布:npm package(internal 公司內部 package)
總結:Design System 是協作的語言
Design System 的核心價值:讓團隊用同一套語言說話,而不是各說各話。
這篇文章涵蓋:
- Design System 與 Component Library 的區別
- Design Tokens 的概念與實作
- 基礎元件與複合元件的建立方式
- Storybook 文件的建立方法
- 建立 Design System 的步驟
下一步:選擇你們團隊最常用的 3 個元件(通常是 Button、Input、Card),把它們抽出來,加上 Design Tokens,開始建立你們的第一個 internal 元件庫。