原生 不能直接替换成 div + icon,因为屏幕阅读器仅识别原生控件的语义与状态;必须保留原生 input 并用 css 隐藏,通过 :checked 伪类和关联 label 实现无障碍交互。

为什么原生 <input type="checkbox"> 不能直接替换成 div + icon
因为屏幕阅读器(如 NVDA、VoiceOver)默认只识别原生表单控件的语义和状态变化。用 div 模拟复选框,哪怕加了 aria-checked="true",若缺少 role="checkbox"、tabindex="0" 和键盘交互逻辑(Space 键切换),用户就无法聚焦、无法触发、也无法得知当前是否选中。
必须保留原生 <input> 并用 CSS 隐藏它
真正的无障碍复选框不是“重写”,而是“包裹 + 增强”。核心做法是:保留不可见但功能完整的原生 <input type="checkbox">,用相邻的 label 控制它,再通过 CSS 把视觉样式画在 label 或伪元素上。
-
<input id="agree" type="checkbox" aria-labelledby="agree-label">—— 必须有id和关联的aria-labelledby(或for属性) -
<label for="agree"><span id="agree-label">我同意条款</span></label>—— 点击文本或任意区域都能触发勾选 - CSS 中对
input[type="checkbox"]使用position: absolute; opacity: 0; width: 1px; height: 1px;隐藏但不移除可访问性
:checked + ::before 是最安全的视觉反馈方案
不要用 JavaScript 动态切换类名来控制“已选中”样式——这会破坏屏幕阅读器对状态的实时感知。原生 :checked 伪类天然同步,且所有主流浏览器都支持其与 label 的联动。
input[type="checkbox"] + label::before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
margin-right: 8px;
vertical-align: middle;
border: 2px solid #666;
border-radius: 4px;
}
input[type="checkbox"]:checked + label::before {
background: #007bff;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='%23fff' d='M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z'/%3E%3C/svg%3E");
}
注意:SVG 要 URL 编码;background-image 仅在 :checked 时出现,确保状态视觉与 DOM 状态严格一致。
键盘操作和焦点管理容易被忽略的细节
即使结构正确,如果没处理好键盘行为,视障用户仍会卡住。原生 <input> 自带 Tab 键聚焦、Space 键切换,但如果你用 JS 阻止了默认行为(比如为了加动画),就等于废掉了无障碍基础。
- 绝对不要给
label加tabindex="0"—— 这会让焦点落在非控件元素上,违反 WCAG 2.1 2.4.3 - 不要用
onclick或addEventListener('click')替代原生点击行为 —— 它不响应键盘 Space - 若需自定义动画,应在
input的change事件里触发,而不是拦截click - 确保焦点样式可见:
input:focus + label::before { outline: 2px solid #007bff; outline-offset: 2px; }
复杂点在于:所有视觉反馈(动画、颜色、图标)必须严格跟随 :checked 和 :focus 的 CSS 状态,而不是 JS 变量或 class 切换。一旦脱钩,辅助技术就再也读不到真实状态了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











