我是做技术文档和前端开发出身的,平时最怕两种设计系统文档:一种是只贴了 Figma 截图,代码怎么写全靠猜;另一种是只有干巴巴的 TypeScript Props 列表,点击后有什么动画、键盘怎么操作、报错状态如何流转一概不提。
一个合格的设计系统(Design System),不能只做「视觉还原」,更要做好「交互规范落地」。今天这篇文章,我就把团队内部跑通的组件交互文档化方案分步骤拆开,直接讲清楚怎么落地。
一、 痛点:为什么只写 API 表格是不够的?
以一个最普通的「带异步提交的表单按钮」为例:
- 静态属性只有:
variant,size,loading。 - 但实际交互涉及:
- 点击后进入
loading状态,此时是否允许重复触发onClick? - 按钮在
loading时,原有的文本是隐藏还是替换为 Spinner? - 键盘通过
Tab键聚焦在按钮上时,按Enter和Space的触发时机是否一致? - 如果提交失败,焦点(Focus)应该留在按钮上还是移动到错误提示处?
- 点击后进入
如果文档里没写明白,三个前端能写出三种不同的交互表现。因此,交互文档必须把「状态流转」和「事件契约」显式化。
二、 核心方案:组件交互文档的「三层结构」
为了让开发者、UI 设计师和 QA 都能看懂,我们把组件文档规范拆成三层:
┌────────────────────────────────────────┐
│ 1. 状态矩阵 (State Matrix) │ -> 罗列视觉与行为状态
├────────────────────────────────────────┤
│ 2. 交互行为契约 (Interaction Contracts) │ -> 触发条件、时序与副作用
├────────────────────────────────────────┤
│ 3. 键盘与无障碍映射 (A11y & Keyboard) │ -> 焦点流转与屏幕阅读器支持
└────────────────────────────────────────┘
1. 状态矩阵(State Matrix)
不要只展示默认状态,必须用表格或 Canvas 平铺所有组合状态:
| 基础状态 | Default | Hover | Active / Pressed | Focus-Visible | Disabled | Loading / Error |
|---|---|---|---|---|---|---|
| 表现 | 常规背景 | 背景加深 5% | 背景加深 10% | 2px 外发光环 | 40% 透明度, 禁用光标 | 替换图标 / 红框提示 |
2. 交互行为契约(Interaction Contracts)
用清单明确「事件 - 响应」逻辑:
- 触发源:Click / Hover / Long-press / Shortcut。
- 状态流转:从
Idle转换至Pending,接口返回后转为Success或Error。 - 边界处理:防抖(Debounce)时间是多少?请求超时后如何回滚?
3. 键盘与无障碍映射(Keyboard & A11y)
直接给出键盘映射表(Keymap),这是最容易被遗漏却极其重要的部分:
Tab/Shift + Tab:进入或离开焦点。ArrowDown/ArrowUp:在列表选项间移动高亮。Enter/Space:选中当前项。Escape:关闭浮层并将焦点归还至触发器(Trigger)。
三、 实战示例:为「可搜索多选下拉框 (Select)」编写交互文档
我们通常使用 Storybook + MDX 来组织文档。以下是一个可以直接套用的文档书写模板:
1. 文档结构示例(MDX 格式)
# Select 选择器 (Searchable Multi-Select)
用于多项数据的检索与挑选。
## 交互行为规范
### 1. 下拉浮层开闭逻辑
- **打开**:点击选择框主体,或在聚焦时按下 `ArrowDown` / `Enter`。
- **关闭**:
- 点击浮层外部区域(Click Outside)。
- 按下 `Escape` 键。
- 单选模式下选中项后自动关闭;多选模式下保持展开。
### 2. 键盘导航时序
```text
[聚焦触发器] --按 Enter--> [展开面板 & 输入框自动聚焦]
|--按 ArrowDown--> [高亮第一项]
|--按 Enter------> [选中/反选该项]
|--按 Escape-----> [关闭面板 & 焦点回到触发器]
```
### 3. 可交互示例 (Interactive Sandbox)
<Canvas of={SelectStories.SearchableMulti} />
2. 代码层:通过 Storybook Play Function 固化交互用例
写在文档里的交互不能只是静态文字,可以通过 Storybook 的 play 函数(基于 Testing Library)把交互过程跑给开发者看,既是文档演示,也是自动化测试:
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within, expect } from '@storybook/test';
import { MultiSelect } from './MultiSelect';
const meta: Meta<typeof MultiSelect> = {
title: 'Components/MultiSelect',
component: MultiSelect,
};
export default meta;
type Story = StoryObj<typeof MultiSelect>;
export const KeyboardSelectionFlow: Story = {
args: {
options: [
{ label: 'Vue.js', value: 'vue' },
{ label: 'React', value: 'react' },
{ label: 'Angular', value: 'angular' },
],
placeholder: '请选择技术栈',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole('combobox');
// 1. 模拟用户点击打开下拉框
await userEvent.click(trigger);
// 2. 验证下拉浮层展开
const listbox = await canvas.findByRole('listbox');
await expect(listbox).toBeInTheDocument();
// 3. 模拟键盘向下移动并回车选中
await userEvent.keyboard('{ArrowDown}');
await userEvent.keyboard('{Enter}');
// 4. 验证已生成 Tag
const tag = await canvas.findByText('Vue.js');
await expect(tag).toBeInTheDocument();
},
};
四、 工具链与落地实践建议
要在团队内长期推行这套方案,不能靠人力死磕,需要配合工具链:
- 底层组件使用无样式交互库(Headless UI)
- 强烈建议基于 Radix UI、React Aria 或 Zag.js 封装。这些库已经处理好了 90% 的焦点管理、键盘导航和屏幕阅读器适配,文档直接继承其 A11y 规范即可,省下大量编写成本。
- 代码即文档(TypeDoc / JSDoc 自动提取)
- 组件 Props 上的 JSDoc 注释必须包含
@default和简明交互说明,构建工具会自动提取生成表格,避免手工同步 Markdown。
- 组件 Props 上的 JSDoc 注释必须包含
- 保持中英文排版规范
- 细节决定体验。文档中的专业术语(如
Props、DOM、Event)与中文混排时,严格保持中英文之间留空格,代码变量用反引号包裹,提升可读性。
- 细节决定体验。文档中的专业术语(如
总结
写设计系统文档,把视觉参数写清楚只是及格线,把交互契约与边界写明白才是分水岭。
先定义状态矩阵,再梳理键盘与事件时序,最后用代码化的用例(如 Storybook Play)固化下来。按这个流程输出文档,前端开发无需再猜逻辑,QA 测试用例一目了然,设计系统的落地一致性自然就上去了。
License: CC BY-NC 4.0
Updated 3 hours ago
Was this article helpful? Give it a like.
0 comments


