用 input:checked 实现树形菜单展开收起,核心是绕过 JS 依赖,利用 checkbox 天然状态与 :checked 伪类联动,支持键盘操作和无障碍访问,但需确保 label-input 正确关联、结构符合兄弟选择器要求,并为多级菜单限定 CSS 作用域。

为什么用 input:checked 而不用 JavaScript
纯 CSS 实现树形菜单展开收起,核心是绕过 JS 依赖,避免事件绑定、状态管理、SSR 渲染不一致等问题。用 input[type="checkbox"] 配合 :checked 伪类,能天然绑定“开/关”状态,且支持键盘空格切换、无障碍聚焦,浏览器兼容性也足够(IE9+)。但要注意:它只能响应用户显式交互(点击/空格),不能通过 JS 直接设置 checked 后触发样式更新——因为 CSS 不监听 JS 属性变更,只响应 DOM 属性变化(input.checked = true 会生效,但 input.setAttribute('checked', '') 才是更稳妥的写法)。
label 和 input 的关联必须正确
展开按钮必须能触发 checkbox 切换,否则整个逻辑失效。常见错误是 label 没绑定到 input,或用了错误的 for 值:
- 确保
label的for属性值与input的id完全一致(大小写敏感) - 或者把
input直接包在label内(此时无需for和id) - 不要用
name或class替代id做关联
错误示例:<input id="menu-1"><label for="MENU-1">→</label>(大小写不匹配);正确写法:<input id="menu-1"><label for="menu-1">→</label> 或 <label><input type="checkbox">→</label>。
用 :checked ~ 控制子菜单显示
子菜单(通常是 ul 或 div)必须紧跟在 input 后面,且在同一层级,才能用兄弟选择器 :checked ~ ul 精准控制。如果结构嵌套过深或中间插入其他元素,选择器就会失效。
立即学习“前端免费学习笔记(深入)”;
- 推荐 HTML 结构顺序:
input→label→ul(子菜单) - 隐藏默认子菜单:
ul { display: none; } - 展开时显示:
input:checked ~ ul { display: block; } - 若需过渡动画,改用
max-height+overflow: hidden,因为display无法过渡
示例片段:
<li>
<input type="checkbox" id="item-1">
<label for="item-1">文件</label>
<ul>
<li><a href="#">新建</a></li>
</ul>
</li>对应 CSS:input#item-1:checked ~ ul { max-height: 200px; opacity: 1; },同时给 ul 设初始 max-height: 0; opacity: 0; overflow: hidden; transition: max-height 0.3s, opacity 0.2s;。
多级嵌套时 class 命名和选择器范围容易失控
二级菜单再套 checkbox,容易因选择器权重或范围错误导致父级展开时子级也意外显示。关键约束有三个:
- 每一级
input必须有唯一id,且对应label的for正确指向 - 子菜单的
ul只响应**最近的同级**input:checked,所以不要让多个input共享同一个~ ul规则 - 建议为不同层级加语义 class,比如
menu-level-1、menu-level-2,并在 CSS 中分开展开规则:.menu-level-1 > input:checked ~ ul、.menu-level-2 > input:checked ~ ul
否则会出现:点一级菜单时,所有后代 ul 全部展开——这是选择器没限定作用域导致的。
真正麻烦的是可访问性和键盘操作:焦点顺序、aria-expanded 同步、子菜单 tabindex 管理,这些纯 CSS 做不到,必须补少量 JS。如果项目要求 WCAG 2.1 AA,:checked 方案只是起点,不是终点。


















