外观
设计系统与组件库:从规范到落地
很多团队都有"组件库",但真正能长期运转的设计系统很少。差别不在组件数量,而在于是否解决了三个问题:设计决策如何统一、组件 API 如何稳定、多人协作如何持续。
这篇文章从 Token、组件设计、文档和治理四个方面,讲一套可以落地的设计系统方法。
Token:把设计决策变成变量
Design Token 是设计系统的最小单元:颜色、间距、字号、圆角、阴影、动效时长。它的价值不是"少写几个色值",而是让设计决策可以被统一修改。
Token 通常分三层:
text
Primitive(原始值) → indigo-500: #6366f1
Semantic(语义) → action-primary: var(--color-indigo-500)
Component(组件级) → button-bg: var(--action-primary)- Primitive 只描述数值,不承载业务含义。
- Semantic 描述用途,例如
text-primary、bg-surface、border-danger。 - Component 只在必要时使用,避免组件层过度耦合。
业务组件应该只消费 Semantic Token,不直接引用 Primitive:
css
:root {
--color-indigo-500: #6366f1;
--color-slate-900: #0f172a;
--color-white: #ffffff;
--color-bg-canvas: var(--color-white);
--color-text-primary: var(--color-slate-900);
--color-action-primary: var(--color-indigo-500);
--color-border-subtle: rgba(15, 23, 42, 0.12);
}
[data-theme='dark'] {
--color-bg-canvas: #0b1120;
--color-text-primary: #e2e8f0;
--color-border-subtle: rgba(226, 232, 240, 0.14);
}这样切换主题只需要覆盖 Semantic Token,不需要改组件样式。
Token 的源头建议放在独立文件中(JSON 或 YAML),通过 Style Dictionary 等工具生成 CSS 变量、TypeScript 类型和 Figma 变量,避免设计稿与代码各维护一份。
组件 API 设计
组件库最常见的失败模式是"props 爆炸":一个 Button 支持十几种变体、二十个布尔值,最终没人能记住组合规则。
用有限枚举表达变体
vue
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
size?: 'sm' | 'md' | 'lg'
loading?: boolean
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
loading: false,
disabled: false
})
const emit = defineEmits<{ click: [event: MouseEvent] }>()
function handleClick(event: MouseEvent) {
if (props.loading || props.disabled) return
emit('click', event)
}
</script>
<template>
<button
:class="['btn', `btn--${props.variant}`, `btn--${props.size}`]"
:disabled="props.disabled || props.loading"
:aria-busy="props.loading"
type="button"
@click="handleClick"
>
<span v-if="props.loading" class="btn__spinner" aria-hidden="true" />
<slot />
</button>
</template>变体用枚举,不用多个布尔值;禁用和加载状态要同时处理可访问性,aria-busy 和 aria-disabled 比单纯改颜色更有意义。
受控与非受控
输入类组件应同时支持受控和非受控模式:
tsx
interface InputProps {
value?: string
defaultValue?: string
onChange?: (value: string) => void
}- 传入
value时由外部控制,组件只触发onChange。 - 只传
defaultValue时组件内部维护状态。 - 不要在两种模式间来回切换,行为会变得不可预测。
组合优于配置
复杂组件优先提供组合能力,而不是不断增加 props:
tsx
<Select value={value} onChange={setValue}>
<Select.Trigger>请选择</Select.Trigger>
<Select.Content>
<Select.Item value="web">Web</Select.Item>
<Select.Item value="app">App</Select.Item>
</Select.Content>
</Select>这种复合组件模式让调用方控制结构,库负责状态、键盘交互和无障碍属性。Headless UI 库(如 Radix、Headless UI、Ark UI)就是这一思路的典型实现:行为逻辑与视觉样式分离。
无障碍不是可选项
组件库必须把基础无障碍能力内置,否则每个业务团队都会重复犯错:
- 所有交互元素可通过键盘访问,焦点状态可见。
- 弹窗、下拉菜单、Tabs 遵循 ARIA 模式,焦点管理正确。
- 表单控件有关联的 label、错误提示使用
aria-describedby。 - 颜色对比度满足 WCAG AA。
- 动效尊重
prefers-reduced-motion。
css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}组件库的一个错误会影响所有业务,无障碍规范应在组件层解决,而不是交给使用方。
文档与示例
组件没有文档,等于没有组件。文档至少包含:
- 使用场景与不适用场景。
- 可运行的示例代码。
- 完整的 props / events / slots 表格。
- 无障碍说明。
- 设计稿与 Token 对应关系。
- 变更记录和迁移指南。
Storybook 适合承载组件开发与文档,每个组件维护 Story:
tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta = {
title: 'Components/Button',
component: Button,
args: { children: '提交' }
} satisfies Meta<typeof Button>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: { variant: 'primary' }
}
export const Loading: Story = {
args: { loading: true }
}视觉回归测试可以在 Storybook 基础上用 Playwright 截图对比,防止样式意外变更。
版本与发布
组件库是公共依赖,破坏性变更的成本很高。发布策略:
- 遵循 SemVer。删除或修改 props、事件、DOM 结构都可能是 breaking change。
- 提供 codemod 或迁移文档,不要让使用方自己猜。
- 废弃 API 先标记
deprecated,保留至少一个大版本。 - 每个版本生成 changelog,关联设计和开发变更。
- 包体积和 Tree Shaking 纳入 CI 检查。
json
{
"sideEffects": ["**/*.css"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./styles.css": "./dist/styles.css"
}
}sideEffects 声明错误会导致 Tree Shaking 失效,或者把必要的样式摇掉,这是组件库打包的高频问题。
治理机制
设计系统不是"设计团队交付一套组件,前端照着实现",而是持续协作:
- 需求入口统一:业务提出需求,先判断是通用能力还是业务特例。
- 设计评审与开发评审:新组件同时评审交互、视觉、API 和无障碍。
- 贡献流程:外部团队可以提交组件,但需遵循规范并有维护者 review。
- 采用率度量:统计业务项目使用设计系统的比例、组件重复率、样式覆盖次数。
- 定期清理:删除无人使用的组件和 Token,避免系统膨胀。
常见坑
- Token 只有一套颜色。没有语义层,换主题或做多品牌时只能全局替换。
- 组件追求大而全。一个组件支持所有业务场景,最终无法维护。
- 业务样式直接覆盖组件内部结构。组件 DOM 变成隐式 API,改结构就破坏业务。
- 无障碍后补。等组件被大量使用后再改,迁移成本极高。
- 文档与实现脱节。示例不随版本更新,使用方被过期文档误导。
- 没有 deprecation 流程。破坏性变更直接发布,业务团队被迫紧急修复。
小结
设计系统由 Token、组件、文档和治理四部分组成。Token 统一设计决策,组件封装交互与无障碍,文档降低使用成本,治理保证长期演进。组件数量不是目标,让团队用一致的方式构建界面才是。