唯一稳定、无障碍、无需JS的方案是隐藏input[type="radio"]并用label承担点击和视觉反馈;display:none或visibility:hidden会使其脱离可访问性树,而opacity:0或clip裁剪可保留交互与语义。

直接隐藏 input[type="radio"],再用 label 承担点击和视觉反馈,是唯一稳定、无障碍、无需 JS 的方案。其他“覆盖层”“透明遮罩”等做法容易破坏焦点、键盘导航或屏幕阅读器支持。
为什么不能用 display: none 或 visibility: hidden
这两种方式会让元素彻底脱离可访问性树:键盘用户按 Tab 键跳过它,屏幕阅读器读不到它,:checked 伪类也失效——等于废掉了单选按钮本身。真正要的是“不可见但可交互”,不是“不可见且不可用”。
-
opacity: 0:保留渲染流和可访问性,但可能触发点击穿透(尤其在重叠布局中) -
position: absolute; clip: rect(0, 0, 0, 0):经典安全方案,裁剪出 0×0 区域,元素仍在 DOM 中、可聚焦、可被:checked匹配 -
width: 0; height: 0; overflow: hidden:部分旧版 Safari 对:checked + label不稳定,不推荐
input + label 结构与选择器限制
必须保证 input 紧邻其 label,且 label 在 input 后面(顺序不能反),否则 input[type="radio"]:checked + label 无法生效。兄弟选择器 + 只匹配紧邻后一个元素,不是任意后续兄弟。
- ✅ 正确结构:
<input type="radio" name="x" id="a"><label for="a">选项A</label> - ❌ 错误结构:
<label for="a">选项A</label><input type="radio" name="x" id="a">(label在前,+失效) - ⚠️ 注意:
for="id"和id必须严格一致,大小写敏感,且整个页面唯一
用 ::before 绘制自定义圆点的要点
伪元素画出来的“圆圈”本质是 label 的装饰,不是原生控件,所以所有样式逻辑(未选中态、选中态、禁用态)都得靠 CSS 条件控制,不能依赖浏览器默认行为。
立即学习“前端免费学习笔记(深入)”;
- 未选中时,
input[type="radio"] + label::before绘制空心圆(border+background: transparent) - 选中时,
input[type="radio"]:checked + label::before改为实心(background-color),并注意padding和background-clip配合缩放中心点 - 禁用状态需额外写:
input[type="radio"]:disabled + label::before,否则视觉上无法体现 disabled - 垂直对齐容易错位:给
label加display: inline-flex; align-items: center比单纯vertical-align更可靠
最易被忽略的是禁用态和键盘焦点样式——很多人只写了鼠标 hover 和 checked,却忘了 :focus 轮廓线、:disabled 灰阶、以及当用户用 Tab 切换到该组时的视觉提示。这些不是“锦上添花”,而是 WCAG 2.1 的强制要求。



















