关于 Design System Architect
面向构建设计系统的团队,涵盖可复用且一致的 UI 基础设计。内容包括设计令牌、组件架构、文档、主题、治理与测试。
主要功能
- 01规划设计令牌结构
- 02设计可复用组件架构
- 03使用 Storybook 编写组件文档
- 04配置主题与品牌变体
- 05管理版本与治理流程
- 06测试组件和视觉变更
适用场景
- 01构建共享组件库
- 02支持浅色和深色主题
- 03为产品团队编写 UI 组件文档
- 04管理设计系统变更与发布
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ daffy0208/ ai-dev-standards --skill design-system-architect安装后在 Claude Code 中输入/design-system-architect确认已加载。
附带文件manifest.yamlREADME.md
详细介绍
设计系统架构师
设计系统是统一的事实来源,能够大规模实现一致性。
核心原则
设计系统不只是组件库,更是设计与工程之间的共通语言。优秀的设计系统能够:
- 加快产品开发(可复用组件)
- 确保产品间的一致性(统一品牌)
- 提升无障碍性(融入组件)
- 实现可扩展性(复合式增长,而非线性增长)
- 减少技术债务(集中维护)
目标: 构建一次,随处使用。维护一次,全面改进。
阶段 1:设计令牌
什么是设计令牌?
设计令牌是设计系统的原子值。 它们是以数据形式存储、供所有平台使用的最小设计决策(颜色、间距、排版)。
令牌的重要性:
- 单一事实来源(修改一次,随处更新)
- 与平台无关(JSON → CSS、iOS、Android 等)
- 可版本化且可追溯(通过 Git 历史记录设计决策)
- 可换肤(浅色/深色模式、品牌变体)
令牌结构
// tokens/colors.json
{
"color": {
"brand": {
"primary": {
"50": { "value": "#eff6ff" },
"100": { "value": "#dbeafe" },
"200": { "value": "#bfdbfe" },
"300": { "value": "#93c5fd" },
"400": { "value": "#60a5fa" },
"500": { "value": "#3b82f6" },
"600": { "value": "#2563eb" },
"700": { "value": "#1d4ed8" },
"800": { "value": "#1e40af" },
"900": { "value": "#1e3a8a" },
"950": { "value": "#172554" }
}
},
"semantic": {
"background": {
"primary": { "value": "{color.neutral.50}" },
"secondary": { "value": "{color.neutral.100}" }
},
"text": {
"primary": { "value": "{color.neutral.900}" },
"secondary": { "value": "{color.neutral.600}" }
},
"feedback": {
"success": { "value": "#10b981" },
"warning": { "value": "#f59e0b" },
"error": { "value": "#ef4444" },
"info": { "value": "#3b82f6" }
}
}
}
}
令牌类别
原始令牌(原始值):
{
"color-blue-500": "#3b82f6",
"space-4": "16px",
"font-size-base": "16px"
}
语义令牌(按用途命名):
{
"color-primary": "{color-blue-500}",
"spacing-default": "{space-4}",
"text-body": "{font-size-base}"
}
组件令牌(特定于组件):
{
"button-padding-x": "{spacing-default}",
"button-background": "{color-primary}",
"button-text": "{color-white}"
}
令牌架构
tokens/
├── primitives/
│ ├── colors.json # Raw color values
│ ├── spacing.json # 4px, 8px, 16px...
│ ├── typography.json # Font sizes, weights
│ ├── shadows.json # Elevation system
│ └── radii.json # Border radius values
├── semantic/
│ ├── colors.json # background-primary, text-secondary
│ ├── spacing.json # spacing-tight, spacing-comfortable
│ └── typography.json # heading-xl, body-md
└── components/
├── button.json # Button-specific tokens
├── input.json # Input-specific tokens
└── card.json # Card-specific tokens
使用 Style Dictionary 转换令牌
安装 Style Dictionary:
npm install --save-dev style-dictionary
config.json:
{
"source": ["tokens/**/*.json"],
"platforms": {
"css": {
"transformGroup": "css",
"buildPath": "dist/css/",
"files": [
{
"destination": "variables.css",
"format": "css/variables"
}
]
},
"js": {
"transformGroup": "js",
"buildPath": "dist/js/",
"files": [
{
"destination": "tokens.js",
"format": "javascript/es6"
}
]
}
}
}
构建令牌:
npx style-dictionary build
输出(variables.css):
:root {
--color-brand-primary-500: #3b82f6;
--color-semantic-background-primary: #fafafa;
--space-4: 16px;
--font-size-base: 16px;
--button-padding-x: 16px;
}
使用令牌实现深色模式
// tokens/semantic/colors-light.json
{
"background": {
"primary": { "value": "#ffffff" },
"secondary": { "value": "#f9fafb" }
},
"text": {
"primary": { "value": "#111827" },
"secondary": { "value": "#6b7280" }
}
}
// tokens/semantic/colors-dark.json
{
"background": {
"primary": { "value": "#111827" },
"secondary": { "value": "#1f2937" }
},
"text": {
"primary": { "value": "#f9fafb" },
"secondary": { "value": "#d1d5db" }
}
}
CSS 输出:
:root {
--background-primary: #ffffff;
--text-primary: #111827;
}
[data-theme='dark'] {
--background-primary: #111827;
--text-primary: #f9fafb;
}
阶段 2:组件架构
原子设计方法论
原子 → 分子 → 有机体 → 模板 → 页面
atoms/
├── Button/
├── Input/
├── Label/
├── Icon/
└── Text/
molecules/
├── FormField/ # Label + Input + Error
├── SearchBar/ # Input + Icon + Button
└── Card/ # Container + Text + Button
organisms/
├── Header/ # Logo + Nav + SearchBar
├── LoginForm/ # FormFields + Button
└── ProductCard/ # Card + Image + Price + CTA
templates/
├── DashboardLayout/ # Header + Sidebar + Content
└── MarketingLayout/ # Header + Hero + Features + Footer
pages/
├── HomePage/ # MarketingLayout + specific content
└── DashboardPage/ # DashboardLayout + widgets
组件结构
components/
└── Button/
├── Button.tsx # Component implementation
├── Button.module.css # Scoped styles
├── Button.stories.tsx # Storybook stories
├── Button.test.tsx # Unit tests
├── Button.types.ts # TypeScript types
├── index.ts # Public exports
└── README.md # Component docs
Button 组件(原子示例)
Button.types.ts:
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'danger'
export type ButtonSize = 'sm' | 'md' | 'lg'
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant
size?: ButtonSize
isLoading?: boolean
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
fullWidth?: boolean
children: React.ReactNode
}
Button.tsx:
import React from 'react'
import styles from './Button.module.css'
import { ButtonProps } from './Button.types'
export function Button({
variant = 'primary',
size = 'md',
isLoading = false,
leftIcon,
rightIcon,
fullWidth = false,
disabled,
children,
className,
...props
}: ButtonProps) {
const classes = [
styles.button,
styles[variant],
styles[size],
fullWidth && styles.fullWidth,
isLoading && styles.loading,
className
].filter(Boolean).join(' ')
return (
<button
className={classes}
disabled={disabled || isLoading}
aria-busy={isLoading}
{...props}
>
{leftIcon && <span className={styles.iconLeft}>{leftIcon}</span>}
<span className={styles.content}>{children}</span>
{rightIcon && <span className={styles.iconRight}>{rightIcon}</span>}
{isLoading && (
<span className={styles.spinner} aria-label="Loading">
<svg className={styles.spinnerIcon} viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" opacity="0.25" />
<path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" strokeWidth="4" fill="none" strokeLinecap="round" />
</svg>
</span>
)}
</button>
)
}
Button.module.css:
.button {
/* Base */
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-family: var(--font-base);
font-weight: 500;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 150ms ease;
user-select: none;
/* Accessibility */
outline-offset: 2px;
}
.button:focus-visible {
outline: 2px solid var(--color-focus);
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
/* Variants */
.primary {
background: var(--button-primary-background);
color: var(--button-primary-text);
}
.primary:hover:not(:disabled) {
background: var(--button-primary-background-hover);
}
.secondary {
background: transparent;
color: var(--button-secondary-text);
border: 1px solid var(--button-secondary-border);
}
.secondary:hover:not(:disabled) {
background: var(--button-secondary-background-hover);
}
.tertiary {
background: transparent;
color: var(--button-tertiary-text);
}
.tertiary:hover:not(:disabled) {
background: var(--button-tertiary-background-hover);
}
.danger {
background: var(--color-error);
color: var(--color-white);
}
.danger:hover:not(:disabled) {
background: var(--color-error-dark);
}
/* Sizes */
.sm {
padding: var(--space-1) var(--space-3);
font-size: var(--text-sm);
height: 32px;
}
.md {
padding: var(--space-2) var(--space-4);
font-size: var(--text-base);
height: 40px;
}
.lg {
padding: var(--space-3) var(--space-6);
font-size: var(--text-lg);
height: 48px;
}
/* Modifiers */
.fullWidth {
width: 100%;
}
.loading {
color: transparent;
}
.spinner {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
inset: 0;
}
.spinnerIcon {
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.iconLeft,
.iconRight {
display: flex;
align-items: center;
}
.content {
display: flex;
align-items: center;
}
FormField 组件(分子示例)
FormField.tsx:
import React from 'react'
import styles from './FormField.module.css'
interface FormFieldProps {
label: string
error?: string
hint?: string
required?: boolean
children: React.ReactNode
}
export function FormField({
label,
error,
hint,
required = false,
children
}: FormFieldProps) {
const inputId = React.useId()
const errorId = `${inputId}-error`
const hintId = `${inputId}-hint`
// Clone child to pass accessibility props
const childWithProps = React.cloneElement(
children as React.ReactElement,
{
id: inputId,
'aria-invalid': !!error,
'aria-describedby': [
hint ? hintId : null,
error ? errorId : null
].filter(Boolean).join(' ') || undefined
}
)
return (
<div className={styles.field}>
<label htmlFor={inputId} className={styles.label}>
{label}
{required && (
<span className={styles.required} aria-label="required">
*
</span>
)}
</label>
{hint && (
<div id={hintId} className={styles.hint}>
{hint}
</div>
)}
{childWithProps}
{error && (
<div id={errorId} className={styles.error} role="alert">
{error}
</div>
)}
</div>
)
}
组件 API 设计原则
1. 合理的默认值:
// ✅ Good: Works with minimal props
<Button>Click me</Button>
// ✅ Good: Customizable when needed
<Button variant="secondary" size="lg" fullWidth>
Click me
</Button>
2. 优先采用组合,而非配置:
// ❌ Bad: Too many props
<Modal
title="Delete Account"
body="Are you sure?"
confirmText="Delete"
cancelText="Cancel"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
// ✅ Good: Composable
<Modal>
<Modal.Header>Delete Account</Modal.Header>
<Modal.Body>Are you sure?</Modal.Body>
<Modal.Footer>
<Button variant="danger" onClick={handleDelete}>Delete</Button>
<Button variant="secondary" onClick={handleCancel}>Cancel</Button>
</Modal.Footer>
</Modal>
3. 受控与非受控模式:
// Uncontrolled (internal state)
<Input defaultValue="hello" />
// Controlled (external state)
<Input value={value} onChange={setValue} />
4. 多态组件:
interface ButtonProps<T extends React.ElementType = 'button'> {
as?: T
// ... other props
}
// Render as button (default)
<Button onClick={handleClick}>Click</Button>
// Render as link
<Button as="a" href="/dashboard">Dashboard</Button>
// Render as Next.js Link
<Button as={Link} href="/about">About</Button>
阶段 3:Storybook 文档
Storybook 设置
安装 Storybook:
npx storybook@latest init
Button.stories.tsx:
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta: Meta<typeof Button> = {
title: 'Atoms/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary', 'danger'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
isLoading: { control: 'boolean' },
fullWidth: { control: 'boolean' },
disabled: { control: 'boolean' },
},
}
export default meta
type Story = StoryObj<typeof Button>
// Primary story
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Button',
},
}
// All variants
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem' }}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="danger">Danger</Button>
</div>
),
}
// All sizes
export const AllSizes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
),
}
// With icons
export const WithIcons: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem' }}>
<Button leftIcon={<Icon name="plus" />}>Add Item</Button>
<Button rightIcon={<Icon name="arrow-right" />}>Next</Button>
</div>
),
}
// Loading state
export const Loading: Story = {
args: {
isLoading: true,
children: 'Loading...',
},
}
// Disabled state
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled',
},
}
// Full width
export const FullWidth: Story = {
args: {
fullWidth: true,
children: 'Full Width Button',
},
}
MDX 文档
Button.mdx:
import { Meta, Story, Canvas, Controls } from '@storybook/blocks'
import * as ButtonStories from './Button.stories'
<Meta of={ButtonStories} />
# Button
Buttons allow users to trigger actions and make choices with a single tap.
## When to Use
- Primary actions (submit forms, complete workflows)
- Secondary actions (cancel, go back)
- Tertiary actions (view details, learn more)
- Dangerous actions (delete, remove)
## Variants
<Canvas of={ButtonStories.AllVariants} />
### Primary
Use for the main action on a page. Limit to one per screen.
### Secondary
Use for less important actions. Can have multiple per screen.
### Tertiary
Use for the least important actions, like "Learn more" links.
### Danger
Use for destructive actions like deleting data.
## Sizes
<Canvas of={ButtonStories.AllSizes} />
- **Small**: Dense UIs, table actions
- **Medium**: Default for most use cases
- **Large**: Hero CTAs, mobile-first designs
## With Icons
<Canvas of={ButtonStories.WithIcons} />
Icons can clarify the button's purpose or indicate direction.
## States
### Loading
<Canvas of={ButtonStories.Loading} />
Show a loading spinner when an async action is in progress.
### Disabled
<Canvas of={ButtonStories.Disabled} />
Disable buttons when an action is not currently available.
## Accessibility
- ✅ Keyboard accessible (Tab, Enter/Space)
- ✅ Focus visible indicator
- ✅ ARIA attributes (aria-busy, aria-disabled)
- ✅ Loading state announced to screen readers
## Props
<Controls />
## Usage
```tsx
import { Button } from '@/components/Button'
function MyComponent() {
return (
<Button variant="primary" onClick={handleClick}>
Click me
</Button>
)
}
```
### Storybook Addons
```bash
# Accessibility testing
npm install --save-dev @storybook/addon-a11y
# Component interactions
npm install --save-dev @storybook/addon-interactions
# Design tokens addon
npm install --save-dev storybook-addon-designs
.storybook/main.ts:
import type { StorybookConfig } from '@storybook/react-vite'
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'storybook-addon-designs'
],
framework: '@storybook/react-vite'
}
export default config
阶段 4:主题与自定义
CSS 变量方案
tokens.css:
:root {
/* Primitives */
--color-blue-500: #3b82f6;
--color-red-500: #ef4444;
--space-4: 16px;
/* Semantic (can be overridden) */
--color-primary: var(--color-blue-500);
--color-danger: var(--color-red-500);
--spacing-default: var(--space-4);
/* Component tokens */
--button-primary-background: var(--color-primary);
--button-primary-text: white;
--button-padding: var(--spacing-default);
}
/* Theme override */
[data-theme='brand-red'] {
--color-primary: #dc2626;
}
[data-theme='dark'] {
--color-primary: #60a5fa;
--button-primary-background: var(--color-primary);
}
主题提供程序(React)
ThemeProvider.tsx:
import React, { createContext, useContext, useEffect, useState } from 'react'
type Theme = 'light' | 'dark' | 'auto'
interface ThemeContextValue {
theme: Theme
setTheme: (theme: Theme) => void
resolvedTheme: 'light' | 'dark'
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined)
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('auto')
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
// Load saved theme
const saved = localStorage.getItem('theme') as Theme
if (saved) setTheme(saved)
}, [])
useEffect(() => {
// Save theme
localStorage.setItem('theme', theme)
// Resolve theme
if (theme === 'auto') {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
setResolvedTheme(isDark ? 'dark' : 'light')
} else {
setResolvedTheme(theme)
}
}, [theme])
useEffect(() => {
// Apply theme to DOM
document.documentElement.setAttribute('data-theme', resolvedTheme)
}, [resolvedTheme])
return (
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme must be used within ThemeProvider')
}
return context
}
多品牌主题
// themes/acme.ts
export const acmeTheme = {
colors: {
primary: '#3b82f6',
secondary: '#8b5cf6'
},
typography: {
fontFamily: 'Inter, sans-serif'
},
spacing: {
unit: 8
}
}
// themes/contoso.ts
export const contosoTheme = {
colors: {
primary: '#dc2626',
secondary: '#f59e0b'
},
typography: {
fontFamily: 'Roboto, sans-serif'
},
spacing: {
unit: 4
}
}
应用主题:
function applyTheme(theme: Theme) {
Object.entries(theme.colors).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--color-${key}`, value)
})
document.documentElement.style.setProperty('--font-base', theme.typography.fontFamily)
document.documentElement.style.setProperty('--space-unit', `${theme.spacing.unit}px`)
}
阶段 5:版本管理与治理
语义化版本控制
MAJOR.MINOR.PATCH
-
MAJOR: 不兼容的变更(v1.0.0 → v2.0.0)
- 移除属性
- 更改属性类型
- 更改默认行为
-
MINOR: 新功能,向后兼容(v1.0.0 → v1.1.0)
- 新属性
- 新组件
- 新变体
-
PATCH: 错误修复(v1.0.0 → v1.0.1)
- CSS 修复
- 无障碍性改进
- TypeScript 修复
更新日志
CHANGELOG.md:
# Changelog
## [2.0.0] - 2024-01-15
### Breaking Changes
- **Button:** Renamed `type` prop to `variant`
- **Input:** Removed `error` prop (use FormField wrapper instead)
### Migration Guide
```typescript
// Before
<Button type="primary">Click</Button>
// After
<Button variant="primary">Click</Button>
```
[1.5.0] - 2024-01-10
新增
- Button: 新增
isLoading属性 - Input: 新增
leftIcon和rightIcon属性 - Card: 新增组件
修复
- Button: 现在所有浏览器中都能显示焦点轮廓
- Input: 占位符颜色现在符合 WCAG 对比度要求
[1.4.1] - 2024-01-05
修复
- Button: 加载旋转图标现在可以正确居中
- Modal: 修复背景遮罩 z-index 问题
### Component Lifecycle
**P0 (Must Have):**
- Button, Input, Label, Text, Icon
- FormField, Card, Modal
**P1 (Should Have):**
- Select, Checkbox, Radio, Switch, Textarea
- Tabs, Accordion, Dropdown, Tooltip
**P2 (Nice to Have):**
- DatePicker, Combobox, Slider, Toggle
- Toast, Drawer, Popover
**P3 (Future):**
- DataTable, Calendar, FileUpload
- Charts, Timeline, Stepper
### Deprecation Strategy
**1. Announce deprecation:**
```typescript
/**
* @deprecated Use `variant` prop instead. Will be removed in v3.0.0.
*/
export interface ButtonProps {
type?: 'primary' | 'secondary' // Deprecated
variant?: 'primary' | 'secondary' // New
}
2. 同时支持两者(并发出警告):
export function Button({ type, variant, ...props }: ButtonProps) {
if (type) {
console.warn('Button: `type` prop is deprecated. Use `variant` instead.')
}
const finalVariant = variant || type || 'primary'
// ...
}
3. 在下一个主要版本中移除:
// v3.0.0 - type prop removed entirely
export interface ButtonProps {
variant?: 'primary' | 'secondary'
}
设计系统治理
设计系统团队:
- 负责人: 整体愿景与路线图
- 设计师: 视觉设计、UX 模式
- 工程师: 实现、工具
- 贡献者: 构建组件的产品团队
贡献流程:
1. Proposal → GitHub issue
2. Design Review → Figma mockup
3. API Design → TypeScript interface
4. Implementation → PR with tests + stories
5. Documentation → README + Storybook
6. Release → Semantic versioning + changelog
提案模板:
## Component Name
**Problem:** What user need does this solve?
**Usage:** When should this be used?
**API Proposal:**
```typescript
interface ComponentProps {
// ...
}
```
设计稿: [Figma 链接]
无障碍: 如何确保此功能具备无障碍性?
考虑过的替代方案: 我们还探索了哪些方案?
---
## Testing Design Systems
### Visual Regression Testing
**Chromatic (Storybook integration):**
```bash
npm install --save-dev chromatic
# Run visual tests
npx chromatic --project-token=YOUR_TOKEN
package.json:
{
"scripts": {
"chromatic": "chromatic --exit-zero-on-changes"
}
}
组件测试
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Button } from './Button'
describe('Button', () => {
it('renders children', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument()
})
it('calls onClick when clicked', async () => {
const user = userEvent.setup()
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click me</Button>)
await user.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('disables button when loading', () => {
render(<Button isLoading>Click me</Button>)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByLabelText('Loading')).toBeInTheDocument()
})
it('applies variant classes', () => {
render(<Button variant="danger">Delete</Button>)
expect(screen.getByRole('button')).toHaveClass('danger')
})
})
无障碍测试
import { axe, toHaveNoViolations } from 'jest-axe'
expect.extend(toHaveNoViolations)
it('has no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
发布与分发
NPM 包设置
package.json:
{
"name": "@company/design-system",
"version": "1.0.0",
"description": "Company design system",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": ["dist", "README.md"],
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"publishConfig": {
"access": "public"
}
}
rollup.config.js:
import typescript from '@rollup/plugin-typescript'
import postcss from 'rollup-plugin-postcss'
export default {
input: 'src/index.ts',
output: [
{
file: 'dist/index.js',
format: 'cjs',
sourcemap: true
},
{
file: 'dist/index.esm.js',
format: 'esm',
sourcemap: true
}
],
plugins: [
typescript({ tsconfig: './tsconfig.json' }),
postcss({
modules: true,
extract: 'styles.css'
})
],
external: ['react', 'react-dom']
}
发布:
npm login
npm version patch # or minor, major
npm publish
使用
npm install @company/design-system
import { Button, Input, Card } from '@company/design-system'
import '@company/design-system/dist/styles.css'
function App() {
return (
<Card>
<Input placeholder="Email" />
<Button variant="primary">Submit</Button>
</Card>
)
}
设计系统检查清单
基础
- 设计令牌(颜色、间距、字体排印、阴影、圆角)
- 令牌转换流程(Style Dictionary)
- 深色模式支持
- 文档站点(Storybook)
组件
- 原子化设计结构(原子 → 有机体)
- 组件 API 指南(合理的默认值、组合)
- 所有组件均提供 TypeScript 类型
- CSS modules 或 styled-components
- 内置无障碍支持(WCAG 2.1 AA)
文档
- 所有组件的 Storybook 故事
- 包含使用示例的 MDX 文档
- Props 表格(自动生成)
- 设计指南(何时使用、何时不使用)
- 代码示例
测试
- 单元测试(覆盖率 70%+)
- 无障碍测试(jest-axe)
- 视觉回归测试(Chromatic)
- 手动测试检查清单
治理
- 语义化版本控制
- 维护变更日志
- 贡献指南
- 组件生命周期(P0 → P3)
- 弃用策略
分发
- 已发布 NPM 包
- 已部署 Storybook
- 安装指南
- 重大变更的迁移指南
工具与资源
令牌管理:
- Style Dictionary(令牌转换)
- Figma Tokens(同步 Figma → 代码)
组件开发:
- Storybook(文档)
- Chromatic(视觉测试)
- React + TypeScript
测试:
- Jest + React Testing Library
- jest-axe(无障碍)
- Playwright(端到端)
构建与分发:
- Rollup 或 tsup(打包)
- NPM(分发)
- Changesets(版本控制)
灵感来源:
- Radix UI(无障碍基础组件)
- Chakra UI(可主题化组件)
- Material UI(全面的系统)
- Ant Design(企业级)
相关技能
visual-designer- 设计基础(颜色、字体排印、间距)accessibility-engineer- WCAG 合规frontend-builder- React 组件模式testing-strategist- 组件测试策略
设计系统永无止境——它会随着产品不断演进。 🎨