单选按钮组无障碍核心在于HTML结构:name必须完全一致,每个radio需唯一id并用for显式关联label,多维网格须用table+aria-labelledby,checked须在初始HTML中声明,隐藏时禁用display:none。

单选按钮组的无障碍访问体验,核心不在 JS 控制或 CSS 美化,而在 HTML 结构是否让屏幕阅读器能准确说出“你正在回答哪道题、当前选中的是哪个选项”。错一个 name、漏一个 id、少一个 aria-labelledby,用户就可能听到“radio button not checked”然后卡住。
必须用相同 name 且拼写完全一致
浏览器靠 name 属性原生管理互斥逻辑,不是靠 JS。大小写、空格、连字符不一致,等于创建多个独立组。
-
name="q1"和name="Q1"是两组,用户可同时选中两个 - 循环渲染时避免硬编码
name="option",应动态生成如name="question-{{id}}" - 用
document.querySelectorAll('input[type="radio"][name="theme"]')快速验证是否全部命中——返回数量必须等于你预期的选项数
每个单选按钮都要有唯一 id + 显式 for 关联
包裹式 在移动端容易因 DOM 变动断开语义绑定,iOS VoiceOver 尤其敏感。显式 for/id 是唯一可靠方式。
-
for="q1-opt-a"必须字节级匹配id="q1-opt-a",尾部空格、大小写、下划线位置都算不一致 - 一组里不能共用一个
id,否则document.getElementById()只返回第一个,点击第 3 个 label 会聚焦到第 1 个 input - SSR 或 React/Vue 渲染时,
id必须带唯一标识(如id="q2-ans-0"),禁用框架默认 id(如 Vue 的v-for不自动提供 id)
多行多列网格必须用 + aria-labelledbyCSS Grid 或 Flex 布局只解决视觉对齐,无法表达“第 2 行 = Question 2,第 4 列 = +1 分”这种二维语义。屏幕阅读器只会读出孤立的 radio,不知道它属于哪道题、代表什么分值。
- 每行用
Question 2 标记问题
- 每列用
+1 标记选项
- 每个
必须同时关联两者:aria-labelledby="q2 col-plus1"
- 不要用
aria-label 覆盖整段文字(如 aria-label="Question 2, +1"),它会屏蔽原生 label 文本,破坏上下文可变性
checked 属性必须出现在初始 HTML 中
用 JS 执行 el.checked = true 或 el.setAttribute('checked', '') 都无法让屏幕阅读器识别默认选中项,也不会触发 aria-checked="true" 同步。
- 服务端渲染或静态 HTML:直接写
- React:用
defaultChecked,别用 checked={true}(否则变成受控组件,切断无障碍链路)
- Vue:用
v-model 绑定初始值,而非在 input 上写 :checked="true"
- 动态切换时,应手动移除同组其他项的
checked 属性(removeAttribute('checked')),而不是只设当前项
最易被忽略的一点:当单选按钮被 display: none 或 visibility: hidden 隐藏时,即使结构正确,它仍会进入可访问性树并干扰焦点流。真正安全的隐藏方式是 position: absolute; clip-path: inset(100%) —— 元素仍在 DOM 中、语义完整、键盘可聚焦,但视觉不可见。
| Question 2 | +1 |
|---|
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











