:checked 仅是状态钩子,真正实现自定义复选框需依赖 ::before/::after 伪元素、精确 DOM 结构(input 与 label 同级紧邻)、position: absolute 定位、内联 SVG 图标及手动处理 disabled/focus 等全部交互状态。

:checked 本身不“模拟”复杂样式,它只是个状态钩子——真正干活的是 ::before 和 ::after 伪元素 + 精确的 DOM 结构 + 位置控制。想做出带动画、渐变、SVG 图标、禁用态、焦点反馈的复选框,关键不在 :checked 多高级,而在怎么绕过它的限制把样式链串稳。
为什么 input:checked + label 会失效?常见结构陷阱
最常踩的坑是 DOM 中间插了其他元素:<input type="checkbox" id="agree"><span>(必选)</span><label for="agree">我已阅读...</label> —— 这里 label 不是 input 的**紧邻兄弟**,:checked + label 完全不匹配。
必须保证 input 和 label 是同级、连续、无间隔:
-
input和label必须在同一父容器内 -
label的for属性值必须与input的id完全一致(大小写敏感) - 若
label在input前面,改用input:checked ~ label(通用兄弟选择器),但不能跨父级 - 避免给
label设pointer-events: none,iOS Safari 会直接失焦
position: absolute 是自定义复选框的刚需,不是可选项
原生 input 隐藏后,label::before 和 label::after 要精准叠在文字左侧,就必须让 label 成为定位上下文:
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
-
label { position: relative; padding-left: 28px; }—— 给伪元素留出空间,防止文字覆盖 label::before { position: absolute; top: 50%; transform: translateY(-50%); left: 0; width: 16px; height: 16px; }:checked + label::after { position: absolute; top: 50%; transform: translateY(-50%); left: 0; }- 不用
absolute就没法让对勾居中对齐方框,文字缩进也容易错位
用 content: "✓" 还是 SVG base64?字体符号的兼容性雷区
Unicode 符号如 "✓" 或 "\2713" 看似简单,但在不同系统渲染差异大:
立即学习“前端免费学习笔记(深入)”;
- iOS 上默认用 SF Pro,Windows 用 Segoe UI,Android 用 Roboto —— 同一字号下粗细、对齐、基线高度都不一致
- 某些安卓 WebView 会把
"✓"渲染成方块或空白 - 推荐方案:用
background-image: url("data:image/svg+xml,...")内联 SVG,可控性强、缩放无损、颜色可继承 - 若坚持用字体符号,至少加
font-family: system-ui, sans-serif;并设line-height: 1; vertical-align: middle;对齐
禁用态和 focus 状态必须手动接管,:checked 不管这些
:checked 只响应选中/取消,它不感知 disabled 或键盘 focus。要实现完整交互体验,得额外写:
input:disabled + label { opacity: 0.6; cursor: not-allowed; }-
input:focus + label::before { box-shadow: 0 0 0 2px rgba(0,123,255,.25); }(注意:原生 input 必须保留可聚焦能力,别用display: none,改用position: absolute; opacity: 0;) input:checked:disabled + label::before { background-color: #ccc; border-color: #999; }- 没这些,用户 tab 导航时就看不到焦点,屏幕阅读器也无法正确播报状态
:checked 只是其中一环,漏掉任意一个状态,就等于把无障碍和移动端体验直接砍掉一半。

















