Figma 开关、复选框与单选框系统(Switch, Checkbox & Radio System)原子组件架构实战:从 14 态流转矩阵、48px 无障碍热区扩充(Hit Slop)、半选态(Indeterminate)到 Variables 布尔状态机与 Dev Mode 前端无障碍代码交付
在任何企业级 Web 平台、移动端 App 或 SaaS 设计系统中,开关(Switch)、复选框(Checkbox)与单选框(Radio) 都是使用频次最高、交互细节最严苛的原子级表单控件(Atomic Form Controls)。它们虽在视觉上仅占数十像素,却直接承载了用户的决策提交与状态切换。
然而,在日常设计系统维护与跨职能团队协作中,基础选择控件往往潜藏着大量工程与体验缺陷:
- 交互状态残缺:大部分设计师仅绘制了选中(Checked)与未选(Unchecked)两种常态,遗漏了 Hover、Pressed、Focus-Visible(无障碍键盘焦点轮廓)以及只读和多维禁用态;
- 触控与点击热区过窄(Hit Area Trap):直接以 16px 或 20px 的视觉图形作为组件物理边界,在移动端与触控屏上极易造成点击失效或误触相邻元素,违背 WCAG 2.2 AA 级无障碍规范;
- 文本换行基线错位:当选项标签包含多行长文本时,若未在 Auto Layout 容器布局指南 中正确配置纵向对齐策略,图元会尴尬地悬浮在多行文本的绝对几何中心,破坏版面阅读流;
- 半选状态(Indeterminate)缺失:在处理树形组织架构、权限管理或多选表格时,无法准确表达“部分子项被勾选”的中介态;
- 研发交付语义断层:前端开发在 Dev Mode 开发模式 中提取到的是无语义的矩形与圆形矢量打组,难以直接无损映射至原生的
<input type="checkbox">、<input type="radio">或现代无障碍组件库。
本文将深入拆解 Switch、Checkbox 与 Radio 的原子物理结构,构建包含 14 种交互态的完整组件集,实战 48px 热区扩充与 Variables 级联逻辑,并输出符合 WAI-ARIA 规范的前端工程代码。
三大表单选择控件的交互心理学与选型矩阵 #
许多产品在设计表单时经常混淆 Switch 与 Checkbox 的使用边界。明确三者的核心交互模型是构建标准化组件库的前提:
| 控件类型 | 核心交互模型 | 生效时机 | 适用场景示例 | 对应 WAI-ARIA 角色 |
|---|---|---|---|---|
| Switch 开关 | 物理拨动开关(二值状态) | 立即生效(无需表单额外点击保存) | 系统深色模式切换、消息推送开启、硬件功能启闭 | role="switch" (aria-checked="true\|false") |
| Checkbox 复选框 | 独立多项复选(支持未选/选中/半选) | 延迟生效(随同父表单 Submit 提交) | 购物车商品多选、权限列表分配、表格批量操作 | role="checkbox" (aria-checked="mixed\|true\|false") |
| Radio 单选框 | 互斥单项单选(同一分组内有且仅有一项) | 延迟或即时(同一 Group 内互斥) | 支付方式选择(微信/支付宝/信用卡)、套餐版本选配 | role="radio" (aria-checked="true\|false") |
┌────────────────────────────────────────────────────────────────────────┐
│ Form Selection Architecture │
├──────────────────────────┬──────────────────────────┬──────────────────┤
│ 1. Switch │ 2. Checkbox │ 3. Radio │
│ (Instant Trigger) │ (Multi-Select Form) │ (Mutually Exclusive)│
│ │ │ │
│ ┌───────────────┐ │ ┌──────┐ │ ╭──────╮ │
│ │ [●] OFF │ │ │ ✔ │ Checked │ ( ● ) Active│
│ │ OFF [●] │ │ ├──────┤ │ ╰──────╯ │
│ └───────────────┘ │ │ ─ │ Indeterminate│ ╭──────╮ │
│ │ ├──────┤ │ ( ) Normal│
│ │ │ │ Unchecked │ ╰──────╯ │
│ │ └──────┘ │ │
└──────────────────────────┴──────────────────────────┴──────────────────┘
14 态流转状态矩阵与变体属性(Component Properties)解耦 #
为确保组件在各种输入设备(鼠标、触控板、键盘、触屏)下均具备严密的可用性,必须在 组件与变体深度指南 中为每个控件建立全状态流转矩阵。
1. 状态维度拆解(State Matrix) #
每个控件均由两组正交维度复合而成:
- Value 状态:
- Checkbox:
Unchecked、Checked、Indeterminate(半选横杠); - Switch:
Off、On; - Radio:
Unchecked、Checked。
- Checkbox:
- Interaction 交互态:
Default(静止常态);Hover(鼠标悬停,底色或描边轻度微调);Pressed / Active(鼠标按下或触摸按压瞬间,带有涟漪或微缩放);Focus-Visible(键盘 Tab 导航选中的高亮无障碍双层焦点环,Outline 2px Offset);Disabled(禁用灰度态,Cursor: not-allowed,文本透明度 40%)。
两组维度交叉组合,构成了严密的 14 态完整架构。
2. 变体属性配置建议: #
在 Figma 主组件中,推荐采用以下清晰命名:
Checked:false/true/mixed(布尔或枚举,推荐用于代码属性对应);State:Default/Hover/Pressed/Focused/Disabled;Size:Medium(默认 20px 图元 / 40px 行高) /Small(16px 图元 / 32px 行高);ShowLabel:true/false(文本插槽开关);Description:true/false(次级辅助说明文本开关)。
48px 无障碍触控热区扩充(Hit Slop)与基线对齐实操 #
在移动设备与小屏幕设备上,苹果 iOS HIG 与谷歌 Material Design 均明确要求可交互元素的最小触控热区不得低于 44×44pt / 48×48dp。若直接将 20px 的复选框交付给工程师,将产生严重的操作困难。
1. 结构化热区透明包裹层(Hit Slop Padding) #
不要直接在裸露的矢量图元上绑定点击交互!标准结构应为:
┌────────────────────────────────────────────────────────┐
│ Selection Control Item (Auto Layout Horizontal: Gap 8px)│
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Hit Target Box (Frame: 44×44px or 48×48px) │ │
│ │ Auto Layout: Align Center / Center │ │
│ │ Fill: None (Transparent) │ │
│ │ Clip Content: OFF │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Visual Indicator (Frame: 20×20px) │ │ │
│ │ │ Border / Background / Check Icon / Thumb │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Text Content Container (Auto Layout Vertical) │ │
│ │ Width: Fill container │ │
│ │ ├─ Label Text (Body Regular 14px, Primary Color) │ │
│ │ └─ Helper Text (Caption 12px, Secondary Color) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
2. 多行文本折行时的基线对齐避坑实操 #
当 Label 文本较长并自动换行时,常见的严重设计错误是将外层 Auto Layout 设置为居中对齐(Align Center),导致勾选框垂直居中在整段多行文本中间。
正确工程对齐方案:
- 外层容器的 Auto Layout 对齐模式强制设置为 Align Top Left(顶部居左对齐);
- 检查首行文本的行高(Line Height)。例如 14px 字体配合 20px 行高时,首行文本的视觉中心距离顶部为 10px;
- 将视觉图元(20px 高度)的顶部内边距或容器上边缘进行微调偏移,保证图元的垂直几何中线与首行文本的基线视觉中线 1:1 像素级水平重合。
更多对齐与响应式容器技巧可参考 Auto Layout 容器布局指南。
Figma Variables 变量绑定与父子半选(Indeterminate)级联状态机 #
利用 Figma 的数值与布尔型变量系统,无需第三方插件即可制作高保真的全选、反选与半选级联原型。
1. 变量定义清单 #
在 Local Variables 面板中建立以下变量集合:
selectAll:Boolean(控制全选复选框的状态,默认false);isIndeterminate:Boolean(控制全选复选框是否显示半选横杠,默认false);itemChecked1:Boolean(子选项 1 选中态,默认false);itemChecked2:Boolean(子选项 2 选中态,默认false);selectedCount:Number(当前选中的子项总数,默认0)。
2. 交互逻辑步骤化配置: #
- 子项点击触发器(Item Click Trigger):
- 子项 1 绑定
OnClick -> Set variable:itemChecked1 = !itemChecked1; - 随之添加条件判断(Conditional):
If (itemChecked1 == true and itemChecked2 == true):selectAll = true, isIndeterminate = false;Else if (itemChecked1 == false and itemChecked2 == false):selectAll = false, isIndeterminate = false;Else:selectAll = false, isIndeterminate = true。
- 子项 1 绑定
- 父级全选点击触发器(Master Checkbox Trigger):
- 父级点击时:若当前
selectAll == true,则将selectAll = false, itemChecked1 = false, itemChecked2 = false, isIndeterminate = false; - 若当前
selectAll == false,则一键将selectAll = true, itemChecked1 = true, itemChecked2 = true, isIndeterminate = false。
- 父级点击时:若当前
- 图层可见性与变体模式联动:
- 将主组件中的“选中”图层绑定至
selectAll,将“半选横杠”图层绑定至isIndeterminate。
- 将主组件中的“选中”图层绑定至
通过上述逻辑,设计团队可以在产品走查或客户汇报中,实时展示完全拟真的企业级多选级联交互。更详尽的变量体系规范参见 Design Tokens 标准命名与结构对照手册。
Dev Mode 前端工程化交付与 WAI-ARIA 无障碍标准 #
在 Dev Mode 开发模式 中交付选择控件时,前端工程师最忌讳设计师使用自定义 <div> 模拟表单。以下是基于标准语义化 HTML5 与现代 Tailwind CSS / React 的无障碍代码交付标准:
1. Checkbox 复选框现代 React 组件规范 #
import React, { useEffect, useRef } from 'react';
interface CheckboxProps {
id: string;
label: string;
description?: string;
checked: boolean;
indeterminate?: boolean;
disabled?: boolean;
onChange: (checked: boolean) => void;
}
export const EnterpriseCheckbox: React.FC<CheckboxProps> = ({
id,
label,
description,
checked,
indeterminate = false,
disabled = false,
onChange,
}) => {
const inputRef = useRef<HTMLInputElement>(null);
// 原生 input 的 indeterminate 属性只能通过 JS DOM 属性赋值
useEffect(() => {
if (inputRef.current) {
inputRef.current.indeterminate = indeterminate;
}
}, [indeterminate]);
return (
<label
htmlFor={id}
className={`group relative flex items-start gap-3 cursor-pointer select-none py-1.5 ${
disabled ? 'cursor-not-allowed opacity-50' : ''
}`}
>
{/* 44px 移动端无障碍可点击热区扩充容器 */}
<div className="flex h-5 w-5 items-center justify-center shrink-0 mt-0.5">
<input
ref={inputRef}
id={id}
type="checkbox"
checked={checked}
disabled={disabled}
onChange={(e) => onChange(e.target.checked)}
className="peer sr-only" /* 隐藏原生外观但保留键盘可聚焦焦点链 */
aria-checked={indeterminate ? 'mixed' : checked}
/>
{/* 自定义视觉渲染容器与 Focus 焦点轮廓 */}
<div
className="h-4 w-4 rounded border border-gray-300 bg-white transition-all
peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-600 peer-focus-visible:ring-offset-2
peer-checked:bg-indigo-600 peer-checked:border-indigo-600
peer-indeterminate:bg-indigo-600 peer-indeterminate:border-indigo-600
group-hover:border-indigo-500"
>
{/* 选中对勾 SVG */}
{checked && !indeterminate && (
<svg className="h-full w-full stroke-white p-0.5" viewBox="0 0 16 16" fill="none">
<path d="M3.5 8.5L6.5 11.5L12.5 4.5" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)}
{/* 半选横杠 SVG */}
{indeterminate && (
<svg className="h-full w-full stroke-white p-0.5" viewBox="0 0 16 16" fill="none">
<path d="M4 8H12" strokeWidth="2" strokeLinecap="round" />
</svg>
)}
</div>
</div>
{/* 文本内容排版容器 */}
<div className="flex flex-col text-sm leading-5">
<span className="font-medium text-gray-900">{label}</span>
{description && <span className="text-gray-500 text-xs mt-0.5">{description}</span>}
</div>
</label>
);
};
2. Switch 开关无障碍 CSS 与 ARIA 属性交付 #
<!-- Switch 交付必须明确包含 role="switch" 与 aria-checked -->
<button
type="button"
role="switch"
aria-checked="true"
aria-label="启用双重身份验证"
class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent
bg-indigo-600 transition-colors duration-200 ease-in-out
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<span
aria-hidden="true"
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow-lg ring-0
transition duration-200 ease-in-out translate-x-5"
></span>
</button>
常见失误与工程化自检清单 #
在最终发布组件库至团队全局库前,建议对照以下关键项进行验收:
- [ ] 热区是否达标:是否已在底层封装了不小于 44×44px 的透明热区;
- [ ] 焦点态(Focus Ring)是否独立存在:是否单独配置了符合 WCAG 2.2 双层高对比度焦点环,杜绝单纯依靠变色来表达键盘聚焦;
- [ ] 图层命名是否 1:1 严格对称:变体切换时图标和文本命名必须完全一致,防止实例属性重置;若遇变体切换重置问题,可查阅 Figma 常见报错与疑难排查手册;
- [ ] 暗色模式与语义 Token 映射:控件边框色与背景色是否全部挂载至语义颜色变量(如
border-control-default、bg-control-checked),详见 Design Tokens 标准命名与结构对照手册。
核心手册与速查工具
高频手册与实用设计生产力工具直达常见问题与答疑 (FAQ)
高频疑难解析与避坑指南完全不需要。绝大多数核心功能在免费 Starter 计划中均可完整体验与实操练习,无需额外付费购买付费团队版。