role="switch" 必须配合 aria-checked="true"/"false" 使用,且需与视觉状态同步;须用非表单元素实现,添加 tabindex="0"、键盘事件监听及 aria-label/labelledby,并确保焦点样式与状态更新完整闭环。

role="switch" 必须配合 aria-checked 属性使用
仅写 role="switch" 不会触发屏幕阅读器识别为开关控件,它会被当作普通静态元素忽略。必须显式设置 aria-checked="true" 或 aria-checked="false",且该值需与视觉状态同步更新。
常见错误是只改样式(比如 toggle 动画),却忘了在 JS 中同步更新 aria-checked —— 此时 NVDA 或 VoiceOver 仍会读作“未选中”,和用户看到的“开”状态矛盾。
- 不要用
checked属性代替aria-checked:原生<input type="checkbox">用checked,但role="switch"是 ARIA widget,只认aria-checked - 初始值不能省略:即使默认“关”,也要写
aria-checked="false",否则部分读屏会读作“未定义” - 避免动态写
aria-checked="0"或"1":必须是字符串"true"/"false"
不能直接套在 上
role="switch" 和原生 type="checkbox" 语义冲突。浏览器会优先按原生语义处理,导致 role 被忽略,或行为异常(例如 Safari + VoiceOver 下可能既读“复选框”又读“开关”)。
正确做法是用非表单元素(如 <div> 或 <code><span></span>)实现视觉开关,并手动绑定点击、空格键、Enter 键响应,再通过 JS 控制 aria-checked 和视觉样式。
- 推荐结构:
<div role="switch" aria-checked="false" tabindex="0"></div> - 必须加
tabindex="0"才能获得键盘焦点,否则无法用键盘操作 - 记得监听
keydown事件处理Space和Enter,仅靠click不够
需要 aria-label 或 aria-labelledby 明确标识功能
单纯一个滑块图形 + role="switch" 没有文本内容,屏幕阅读器只会读“开关”,用户完全不知道它控制什么。必须提供可访问的标签。
优先用 aria-labelledby 关联附近可见文字(比如旁边的 <label></label>),比 aria-label 更利于维护和翻译。
- 推荐写法:
<label id="wifi-label">Wi-Fi</label><div role="switch" aria-labelledby="wifi-label" aria-checked="false"></div> - 避免
aria-label="开启 Wi-Fi"这类带动作动词的描述:开关本身是状态控件,应描述状态(“Wi-Fi 已关闭”)或功能(“Wi-Fi”),动作由用户决定 - 如果 label 文本较长或含格式,用
aria-describedby补充说明更合适
视觉反馈与焦点样式不可省略
很多团队只顾动画效果,却删掉 :focus 样式或用 outline: none 一了百了。这会让键盘用户彻底迷失当前操作位置,尤其在表单中多个开关并排时。
ARIA switch 的可访问性不只靠属性,还依赖可见的交互反馈链:键盘聚焦 → 视觉高亮 → 空格切换 → 状态更新 → 读屏播报。任一环断裂,就等于没做。
- 确保
tabindex="0"元素有清晰的焦点轮廓(可用box-shadow替代outline,但别隐藏) - 切换状态后,除了更新
aria-checked,还要立刻更新 class(如is-on)驱动 CSS 变化 - 不要依赖
:checked + .switch-thumb这类伪类选择器:因为不是原生 checkbox,这些规则根本不会生效











