用 label + input[type="checkbox"] 实现汉堡菜单可免 js 控制显隐,依赖 :checked 伪类与兄弟选择器驱动 css 过渡,需视觉隐藏 checkbox(opacity:0+absolute)而非 display:none 以保可访问性,但焦点管理仍需少量 js 辅助。

为什么用 label + input[type="checkbox"] 实现汉堡菜单
因为不需要 JS 就能控制显隐状态,浏览器原生支持、语义清晰、可访问性好(配合 aria-expanded 和 for/id 关联后,屏幕阅读器能正确识别开关状态)。关键在于:勾选 checkbox 触发样式变化,label 点击直接操作 checkbox,避免手动写 JS 绑定 click 事件。
input[type="checkbox"] 必须隐藏但不能 display: none
如果用 display: none,该元素会脱离可访问性树,label 的关联失效,键盘 Tab 无法聚焦,屏幕阅读器也读不到。正确做法是视觉隐藏但保留可访问性:
input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 1px;
width: 1px;
overflow: hidden;
}
- 用
opacity: 0+position: absolute确保它还在文档流中、可聚焦、可被label控制 - 别加
visibility: hidden—— 它仍占空间且不可聚焦,行为不一致 - 别依赖
pointer-events: none,否则点击label无法触发 checkbox 切换
用 :checked 伪类驱动菜单展开/收起
核心逻辑是:当 input 被勾选时,通过兄弟选择器(~)或相邻选择器(+)控制导航栏的显示状态。结构必须保证 DOM 顺序合理:
<input type="checkbox" id="nav-toggle"><label for="nav-toggle">☰</label> <nav class="nav-menu"><ul><li><a href="#">首页</a></li></ul></nav>
对应 CSS:
nav.nav-menu {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
input#nav-toggle:checked ~ nav.nav-menu {
max-height: 300px; /* 设为足够容纳菜单项的高度 */
}
- 优先用
max-height过渡而非display—— 后者无法动画;height: auto也不能过渡,所以得设具体值或足够大的固定值 - 若菜单项高度动态变化(比如响应式文字换行),
max-height值需留余量,否则出现截断 - 移动端需加
touch-action: manipulation到label,减少点击延迟
响应式断点与触摸友好细节
汉堡菜单只应在小屏出现,但 CSS 媒体查询和交互反馈必须协同处理:
- 在桌面断点(如
min-width: 768px)里,把input、label都设为display: none,同时让nav恢复为水平布局 -
label必须有明确的点击热区(至少 44×44px),加padding或min-width/min-height,避免 iOS Safari 点不中 - 收起菜单后,应自动移除焦点(否则键盘用户按 Tab 会跳进已隐藏的菜单),这需要 JS 辅助:
input.addEventListener('change', () => { if (!this.checked) this.nextElementSibling?.focus(); })—— 但注意:纯 CSS 方案无法解决焦点管理,这是它唯一的硬伤
真正难的不是结构,而是让「看不见的 checkbox」始终处于可预期的状态:它得被聚焦、能被空格键切换、收起后不卡住焦点、缩放字体时不溢出。这些细节不写 JS 很难面面俱到。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











