Toggle and Switch 设计系统
目的
Toggle and Switch 设计系统提供了一个标准化框架,用于在 Web 界面中设计、定位和实现二进制状态控件。开关模拟了物理的开/关切换操作,可以即时触发或切换状态变化(如设置偏好或功能状态),无需手动提交表单(如点击“保存”)。
如果没有专门的开关设计系统,界面常常会误用开关代替复选框,无法满足 WCAG 在禁用或非活跃状态下的对比度要求,缺少清晰的状态标签,创建的触摸目标对移动端屏幕来说过小,或者在状态变化时引发意外的布局偏移和背景副作用,而没有即时的视觉反馈。
该系统制定了开关几何尺寸、轨道和滑块的比例、标签对齐方式、状态颜色映射、键盘可访问性(role="switch")、触摸热区扩展、响应式列表布局以及异步待处理/加载状态的规则。
使用场景
- 应用设置与偏好: 切换即时系统偏好(例如,“推送通知”、“深色主题”、“自动保存草稿”或“双因素认证”)。
- 订阅与计费周期选择: 在月度与年度计费周期之间切换定价卡片层级,并内联显示折扣标签。
- 数据表格行控制: 直接在数据表格行中启用或禁用个别用户访问、API 集成、Webhook 或自动规则。
- 功能标志与系统模式开关: 在开发者仪表板中切换运行状态(例如,“维护模式”、“调试日志”或“Beta 功能”)。
- 隐私与 Cookie 同意面板: 在抽屉或模态对话框中管理精细的跟踪权限(例如,“分析 Cookie”、“营销 Cookie”)。
何时不使用
- 多选项选择: 如需在 3 个或更多互斥选项中选择,请使用
segmented-control-system或custom-select-and-combobox-system。 - 需要明确确认的表单提交: 对于在标准多字段表单中需要点击“提交”按钮的协议条款(例如,“我接受条款和条件”),请使用标准复选框(
form-design-system)。 - 非即时生效的二元选择: 如果切换控件后必须点击辅助的“应用”或“保存更改”按钮才能生效,则应使用复选框而非开关。
- 表格中的批量选择: 对于需要批量操作的多个项目选择(例如,“删除选中项”),请使用复选框(
data-table-ui-system)。
输入
- 状态定义: 二元状态参数(关/未选中/
false与 开/选中/true)。 - 上下文与执行模式: 即时客户端状态更改(例如,深色主题开关)与异步服务器端的副作用(例如,API Webhook 启用/禁用)。
- 标签结构: 内联标签文本、辅助说明/帮助文本以及状态徽章要求。
- 布局上下文: 独立设置卡片、紧凑表格行、多选项偏好组或英雄定价开关。
- 主题与 Token 调色板: 表面颜色、活跃轨道颜色、非活跃轨道颜色、滑块颜色以及焦点环偏移 token。
输出
- 开关几何与 Token 规范: 轨道宽度、高度、滑块直径、圆角半径和过渡时间的空间尺寸(
width: 44px、height: 24px、thumb: 20px)。 - 状态与对比度蓝图: 确保在默认(关)、活跃(开)、悬停、聚焦可见、禁用和待处理状态下,轨道和滑块满足最低 WCAG 3:1 图形对象对比度的调色板 token。
- 结构布局与排版规范: 开关元素、主标签(
h4/span)、描述文本和可选状态指示器(ON/OFF)之间的间距关系。 - 可访问性与 ARIA 映射: 语义化的 HTML
<button role="switch" aria-checked="true|false">结构,并关联屏幕阅读器标签(aria-labelledby/aria-describedby)。 - 异步处理协议: 用于后台网络验证的视觉加载状态规范(滑块内旋转器或降低轨道透明度)。
工作流程
1. 确定控件语义(开关 vs. 复选框)
在视觉设计之前,确认交互意图符合物理开关的心智模型:
- 即时效果: 切换开关会立即生效(或触发即时后台请求)。
- 独立操作: 独立运行,无需明确的“保存”或“提交”确认按钮。
- 开/关状态隐喻: 表示打开或关闭某个功能或状态,而不是在多字段问卷中做选择。
2. 建立空间几何与触摸边距
设计开关组件时,比例应遵循移动端触摸指南,同时在桌面端保持视觉和谐:
[ 非活跃 / 关闭轨道 ] [ 活跃 / 开启轨道 ]
+--------------------------+ +-------------------------+
| (滑块) | -----> | (滑块) |
| 20px | | 20px |
+--------------------------+ +-------------------------+
<-------- 44px ------------> <-------- 44px ----------->
24px 高度 24px 高度
- 标准开关尺寸:
- 轨道宽度:
44px(或2.75rem) - 轨道高度:
24px(或1.5rem) - 轨道圆角:
999px(完全药丸形状) - 滑块直径:
20px(所有边留有2px内部轨道内边距) - 滑块圆角:
50%(圆形)
- 轨道宽度:
- 紧凑/密集开关尺寸(表格行):
- 轨道宽度:
36px - 轨道高度:
20px - 滑块直径:
16px(留有2px内边距)
- 轨道宽度:
- 触摸目标包围:
- 在移动设备上,交互点击区域必须通过伪元素(
::before/::after)或内边距扩展到至少 44x44px,以防止误触。
- 在移动设备上,交互点击区域必须通过伪元素(
3. 定义状态颜色 Token 与对比度指标
为满足 WCAG 2.1 / 2.2 AA 非文本对比度要求(SC 1.4.11 - 最小 3:1 对比度),应遵循严格的颜色 token 配对:
- 非活跃 / 关闭状态:
- 轨道背景:中性灰色表面(
#E2E8F0/ 深色模式:#334155)。 - 轨道边框:1px 边框(
#CBD5E1/ 深色模式:#475569)以确保与浅色背景表面达到 3:1 对比度。 - 滑块颜色:纯白色或浅色表面(
#FFFFFF/ 深色模式:#F8FAFC)。 - 可选滑块内图标:月亮/太阳或叉/勾图标(
12px),用于双重视觉反馈。
- 轨道背景:中性灰色表面(
- 活跃 / 开启状态:
- 轨道背景:高对比度的品牌或系统操作颜色(
#2563EB主蓝色或#16A34A成功绿色)。与背景页面的对比度必须超过 3:1。 - 轨道边框:透明或匹配的活跃边框。
- 滑块颜色:纯白色(
#FFFFFF),具有平滑的 CSS 过渡效果(transform: translateX(20px),持续200ms cubic-bezier(0.4, 0, 0.2, 1))。
- 轨道背景:高对比度的品牌或系统操作颜色(
- 悬停与聚焦状态:
- 悬停:轨道亮度轻微变化(+5% 或 -5%)或滑块阴影放大(
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.15))。 - 聚焦可见:明显的 2px 偏移焦点环(
outline: 2px solid #2563EB; outline-offset: 2px)。
- 悬停:轨道亮度轻微变化(+5% 或 -5%)或滑块阴影放大(
- 禁用状态:
- 轨道和滑块透明度:降低至
0.5或0.4,并设置cursor: not-allowed。
- 轨道和滑块透明度:降低至
4. 布局标签对齐与描述层级
开关应始终配备清晰的文本标签。切勿在缺乏可访问标签或可见上下文的情况下呈现孤立的开关。
- 水平行布局(默认偏好卡片):
- 将文本块放在左侧(标题 + 描述),开关放在右侧边缘。
- 将开关轨道的垂直中心与标题文本的第一行对齐(不要在多行描述中居中)。
- 标题文本与开关元素之间保持至少
16px的间距。
- 表单内联布局:
- 将开关放在左侧,标签文本放在右侧(
gap: 12px),适用于开关集成在密集表单字段中的情况。
- 将开关放在左侧,标签文本放在右侧(
- 定价层级开关布局:
- 将开关水平居中放置于两个明确的标签选项之间(例如,“月度”——[ 开关 ]——“年度(节省 20%)”)。点击任一文本标签或开关均可切换活跃状态。
5. 处理异步副作用与加载状态
当开关触发异步 API 调用时(例如,更改云资源配置或启用集成):
- 乐观视觉状态: 立即将滑块滑动到目标位置(
aria-checked="true")。 - 待处理反馈: 在滑块内渲染微妙的旋转器覆盖层,或在网络延迟期间略微降低轨道透明度。
- 错误恢复: 如果服务器返回错误,显示内联的 toast/alert 消息,并平滑地将滑块动画回之前的状态,同时将
aria-checked设置为false。
6. 实现 WCAG AA 可访问性模式
确保所有浏览器引擎支持原生键盘导航和屏幕阅读器:
- 语义化 HTML 标记:
<button type="button" role="switch" aria-checked="true" id="push-notifications-switch" aria-labelledby="push-notifications-label" aria-describedby="push-notifications-desc" class="toggle-switch"> <span class="toggle-switch__track"> <span class="toggle-switch__thumb"></span> </span> </button> - 键盘交互:
- 可通过
Tab键导航聚焦。 - 按
Space或Enter键切换aria-checked状态(true<->false)并触发关联的处理函数。
- 可通过
- 屏幕阅读器朗读:
- 屏幕阅读器会明确朗读:“推送通知,开关,开” 或 “推送通知,开关,关”。
- 如果开关已经通过原生 ARIA 状态表明了状态,不要在标签中重复写入“开/关”之类的文本。
决策规则
开关 vs. 复选框 vs. 分段选择器选择矩阵
| 场景 / 需求 | 推荐控件 | 原因 |
|---|---|---|
| 单个二元选择,即时生效(例如,深色主题) | 开关 | 即时执行模型匹配物理开关。 |
| 单个二元选择,位于提交表单中(例如,接受条款) | 复选框 | 更改在点击“提交”按钮后才生效。 |
| 从列表中多选项目(例如,选择 5 封邮件) | 复选框 | 标准的批量选择行为。 |
| 在 2 种不同模式之间选择(例如,网格视图 vs. 列表视图) | 分段选择器 | 两种状态标签并排可见。 |
| 在移动端列表视图中设置选项的开/关 | 开关 | 清晰的滑块目标和明确的状态反馈。 |
标签位置矩阵
| 布局上下文 | 开关位置 | 标签位置 | 空间行为 |
|---|---|---|---|
| 设置偏好列表 | 右对齐 | 左对齐 | 行容器使用 justify-content: space-between。 |
| 内联表单字段 | 左对齐 | 右对齐 | display: inline-flex; align-items: center; gap: 12px。 |
| 英雄定价开关 | 居中对齐 | 左、右两侧分布 | 两侧分别为“月度”和“年度”文本选项。 |
约束条件
- 可访问性(WCAG 2.1 / 2.2 AA):
- 在开和关两种状态下,轨道边框/背景以及滑块相对于页面背景的最小非文本对比度必须达到 3:1。
- 所有关联标签和描述辅助文本的最小文本对比度必须达到 4.5:1。
- 焦点环与周围背景的对比度必须至少达到 3:1,且厚度至少为 2px。
- 触摸目标:
- 在触摸设备上,交互触摸区域必须至少为 44x44px(WCAG 2.5.5 / 2.5.8)。
- 颜色独立性:
- 颜色不得作为状态的唯一指示符。滑块的物理空间位置(左 = 关,右 = 开)以及语义化的
aria-checked值必须独立于颜色传达状态。
- 颜色不得作为状态的唯一指示符。滑块的物理空间位置(左 = 关,右 = 开)以及语义化的
常见失败模式
- 在提交表单中误用开关: 在表单中使用开关,但更改在点击单独的“保存”按钮后才生效,使用户感到困惑(期望即时应用)。
- 轨道对比度不足: 在白色背景上使用浅灰色轨道(
#F1F5F9)且没有边界边框,导致低视用户看不到开关。 - 仅通过颜色传达状态: 仅依靠红色和绿色轨道颜色,而不移动滑块位置或更新 ARIA 属性。
- 触摸目标过小: 将点击区域限制在 36x20px 的视觉轨道内,导致在移动端视窗上误触和漏触。
- 缺少可访问标签: 渲染独立的图标开关,但没有
aria-label或aria-labelledby引用。 - 多行文本重叠: 将开关垂直居中于较长的 4 行描述段落,破坏了与主标题的视觉对齐。
验证标准
- 开关使用语义化的 HTML
<button role="switch" aria-checked="true|false">结构。 - 在 ON 和 OFF 两种状态下,轨道和滑块与背景的对比度满足 3:1 最小比率。
- 键盘导航(
Tab、Space、Enter)能平滑切换开关状态并更新aria-checked。 - 触摸点击区域通过不可见内边距或伪元素扩展到至少 44x44px。
- 开关配有可见、高对比度的标签文本,并通过
aria-labelledby关联。 - 开关位置在 150ms–250ms 时间内平滑过渡(
transform: translateX()),并符合prefers-reduced-motion要求。 - 响应式设置列表在窄移动端屏幕上保持适当的间距(
gap: 16px),文本不会重叠。