浏览器禁止对 radio/checkbox 使用 ::before/::after,因其为替换元素且无渲染盒;需隐藏原生控件,用 label 包裹并借助 :checked + label::before 实现自定义样式。

为什么不能直接用 input[type="radio"] 的伪元素?
因为浏览器禁止对 input[type="radio"] 和 input[type="checkbox"] 直接添加 ::before / ::after——它们是“替换元素”,默认没有渲染盒,伪元素无处依附。强行写上去,CSS 会静默忽略。
所以必须绕开:隐藏原生控件,用相邻的 label 或父容器承载自定义样式,再通过 :checked + + 或 ~ 选择器联动视觉状态。
用 label 包裹实现单选框自定义(推荐方案)
这是最稳定、语义正确、无障碍友好的方式。核心是把 input 放进 label,用 display: none 隐藏它,再用 label::before 绘制自定义圆点。
-
input必须有name属性,否则无法互斥 -
label不需要for属性——包裹结构天然绑定点击行为 - 自定义圆点建议用
border-radius: 50%+background,避免用content: "●"(字体渲染不一致) - 选中态用
input:checked + label::before触发,注意顺序:input必须在label前面(或用~配合兄弟结构)
input[type="radio"] { display: none; }
input[type="radio"] + label::before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
border: 2px solid #999;
border-radius: 50%;
margin-right: 8px;
}
input[type="radio"]:checked + label::before {
background: #007bff;
border-color: #007bff;
}
:checked + ~ 选择器的坑:DOM 位置必须严格满足
如果不想用包裹结构(比如 input 和 label 是分离的兄弟节点),就得依赖 ~(通用兄弟选择器)。但它只匹配「后面」且「同级」的元素,而且中间不能插其他非 label 元素。
- ✅ 正确:
input→span→label→label:只有第一个label能被input ~ label选中 - ❌ 错误:
input→div→label:div阻断了兄弟关系,~失效 - ⚠️ 注意:Vue/React 中动态插入的元素可能打乱 DOM 顺序,导致样式突然失效
复选框也能用同样逻辑,但视觉反馈要区分「未选中」「半选中」
复选框常需支持 indeterminate 状态(如父子节点全选时的中间态),而原生 input 的 indeterminate 是 JS 属性,无法用 CSS 伪类捕获。必须手动加 class 控制:
- 用
input[type="checkbox"].indeterminate + label::before模拟横杠效果(background: currentColor+height: 2px) - 不要依赖
:indeterminate伪类——它兼容性差(Safari 一直不支持) - 移动端点击热区至少保留 44×44px,用
label的padding扩展,别只靠::before尺寸
实际项目里最容易漏的是焦点管理:自定义后 tab 键无法聚焦到视觉控件上。得给 label 加 tabindex="0" 并监听 keydown 模拟空格切换,不然键盘用户直接卡住。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











