抽屉菜单不必用 ,但需建立真正的模态上下文:背景不可聚焦、焦点在抽屉内循环、关闭后焦点回触发按钮、aria-expanded 与可见状态严格同步。

抽屉菜单必须用 <dialog></dialog> 吗?
不用。但如果你用 <dialog></dialog>,浏览器会自动处理焦点隔离、ESC 关闭、背景禁用和返回焦点——这是最省心的模态语义方案。不过它不支持左/右滑出动画(只支持居中弹出),所以多数移动端抽屉仍得靠 transform + 语义补全。
关键不是“用不用 <dialog></dialog>”,而是“是否建立了真正的模态上下文”。手动实现时,必须同时满足:
• 背景内容不可聚焦(inert 属性或 JS 动态设 tabindex="-1")
• 抽屉内第一个/最后一个可聚焦元素之间 Tab 循环(不能靠监听 keydown 强制 focus)
• 点击遮罩、按 ESC、调用关闭逻辑后,焦点必须回到触发按钮
• 触发按钮的 aria-expanded 必须与抽屉可见状态严格同步
aria-controls 和 aria-expanded 怎么配对才不翻车?
只写 aria-controls="nav-drawer" 是错的。如果抽屉展开时主内容被隐藏(而非仅位移),那 aria-controls 就不该指向主内容区——它只该指向“被控制显隐的导航容器”本身。
正确做法:
• 触发按钮始终只控制一个目标:aria-controls="main-nav"
• 抽屉容器加 role="navigation" 和 aria-labelledby 指回按钮 id
• 主内容区(.content)不参与 ARIA 控制链;它是否隐藏,由 CSS visibility 或 inert 决定,而非 ARIA 状态
• aria-expanded 只反映按钮当前是“展开菜单”还是“收起菜单”,值为 true 或 false,不能是 "hidden" 或其他字符串
立即学习“前端免费学习笔记(深入)”;
为什么键盘用户 Tab 到抽屉末尾就“消失”了?
因为焦点没被拦住,而是跑到了页面底部或地址栏。这不是浏览器 bug,是你没建立焦点边界。
纯 CSS 抽屉(如 checkbox + ~)天然无法拦截焦点,必须配合 JS 补足:
• 在抽屉打开时,遍历所有可聚焦元素(a[href], button, input, [tabindex] >= 0),存入数组
• 监听 keydown 事件,当检测到 Tab 键且当前焦点在最后一个元素上时,event.preventDefault() 并 focus() 第一个
• 同理,Shift + Tab 到第一个时,跳到最后一个
• 注意:不要在 focusin 里做循环判断,容易死循环;只在 keydown 中拦截 Tab 流向
移动端点击汉堡按钮没反应,可能卡在哪?
真机上最常被忽略的是 touch-action 和 cursor 缺失,导致 iOS Safari 不识别 tap 事件。
检查这三项:
• 汉堡按钮必须有 cursor: pointer 和 touch-action: manipulation
• 如果用 label 触发 checkbox,确保 label 的 for 属性精准匹配 input 的 id,且 input 在 DOM 中位于 label 之前(否则 ~ 选择器失效)
• 遮罩层(.overlay)不能设 pointer-events: none,否则点它不会触发 checkbox:checked 状态切换
复杂点在于:无障碍不是加一堆 ARIA 就完事,而是让行为与语义严丝合缝。比如,抽屉内容滚动时若穿透到背景页,视觉上没问题,但屏幕阅读器会读取两处内容——这时 inert 比 aria-hidden 更可靠,因为它真正冻结 DOM 交互能力。



















