单选按钮组无障碍核心是语义完整:必须用完全一致的name(全小写、短横线分隔)统一分组,每个选项配唯一id+显式label[for]关联,多维量表用table+aria-labelledby,checked须HTML属性写死。

单选按钮组的无障碍问题,核心不在“能不能点”,而在于“屏幕阅读器能不能说清楚、键盘用户能不能高效操作”。光靠 CSS Grid 或 flex 布局对齐,会直接切断语义链路——这是绝大多数人踩坑的起点。
必须用 name 统一分组,且大小写/空格零容错
浏览器只认 name 属性完全一致(字节级相同)的一组 input[type="radio"] 为互斥组。漏写、拼错、大小写混用(比如 q1 和 Q1),都会让某个按钮变成“幽灵单选”:能点、不取消别人、读屏报成复选框。
- 所有同组项的
name必须全小写、短横线分隔,例如user-preference - 服务端渲染或模板循环中,避免用变量拼接
name,尤其注意作用域污染导致值为空或错乱 - 快速验证:在控制台运行
document.querySelectorAll('input[type="radio"][name="user-preference"]').length,结果应等于你预期的选项数
每个单选按钮都要有独立 id + 显式 label[for] 关联
仅靠包裹式 <label><input>文本</label> 在多选项场景下不可靠——屏幕阅读器无法区分“男”和“女”各自对应哪个控件,尤其当它们被拆到不同容器里时。
- 正确写法是为每个
input分配唯一id(如gender-m、gender-f),再用label[for]显式绑定 -
for和id必须字节级一致,大小写、连字符、顺序都不能差 - SSR 或 Vue/React 循环渲染时,
id必须带唯一标识(如gender-m-{{uid}}),否则所有for都指向第一个元素
网格类评分表必须用 <table> + aria-labelledby
像 Likert 量表这种「多问题 × 同一选项集」的布局,CSS Grid 只解决视觉对齐,却让屏幕阅读器丢失“第2行第4列 = 问题2,得分为+1”这一关键上下文。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
立即学习“前端免费学习笔记(深入)”;
- 用
<table>是语义正解:<th scope="row">表示问题,<th scope="col" id="plus1">表示选项值 - 每个
input的aria-labelledby同时引用行头id和列头id,例如aria-labelledby="q2 plus1" - 避免用
aria-label硬编码文本,它会覆盖原生标签,且无法随语言/上下文动态更新
checked 属性必须作为 HTML attribute 写死,不能靠 JS 动态赋值
用 document.getElementById('r1').checked = true 设置默认选中,视觉上有效,但会破坏无障碍树:屏幕阅读器看不到初始选中状态,:checked 伪类也不触发,aria-checked 更不会同步。
- 静态页或 SSR 输出中,必须显式写出
checked属性:<input type="radio" name="theme" value="dark" checked> - React 中用
defaultChecked,Vue 中用v-model绑定初始值,而非:checked="true" - 后续切换逻辑,应通过修改同组其他项的
checked属性(removeAttribute('checked'))来模拟,而不是只动一个
真正卡住人的从来不是语法怎么写,而是 name 是否全局一致、id 是否在动态渲染中保持唯一、aria-labelledby 引用的是否是真实存在的 id 节点——这些地方一出错,无障碍就断在第一层。


















