纯CSS :hover多级下拉菜单对键盘和屏幕阅读器用户不可用,因浏览器无法识别其逻辑菜单结构,Arrow键无法流转焦点,屏幕阅读器仅视作孤立列表项而非具上下文的菜单;必须手动实现WAI-ARIA Menu Pattern,正确设置aria-haspopup="menu"、role="menu"/"menuitem"、aria-labelledby及焦点管理逻辑。

纯 CSS 实现的多级下拉菜单对键盘和屏幕阅读器用户基本不可用,这不是兼容性问题,而是设计缺陷——浏览器无法从 DOM 结构中推断出“菜单”语义,ArrowDown、Tab、Enter 等行为全无响应。
为什么 :hover 菜单在无障碍测试里直接挂掉
屏幕阅读器看到的只是一堆孤立的 <li> 和 <a>,没有“菜单上下文”,也不会读出“子菜单有 3 项”或“此项可展开”。更关键的是:aria-haspopup 如果设成 "true"(而非 "menu"),多数读屏器会跳过整个子菜单;漏掉 role="menuitem",焦点就根本进不去子项。
-
aria-haspopup="true"是常见错误写法,必须是"menu" - 子菜单容器(如
<ul>)必须带role="menu"和aria-labelledby(值为触发项的id) - 禁用菜单项不能只靠
opacity: 0.5,得同时加aria-disabled="true"和tabindex="-1"
keydown 事件里怎么手动实现 Arrow 键流转
浏览器不会自动帮你处理多级菜单的焦点跳转。你得在 role="menu" 容器内监听 keydown,并按 WAI-ARIA Menu Pattern 手动计算下一个可聚焦元素:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
ArrowDown/ArrowUp:遍历所有role="menuitem",跳到下一个/上一个(循环) -
ArrowRight:若当前项有展开的子菜单,直接聚焦其第一个menuitem -
ArrowLeft:收起当前子菜单,并把焦点移回父级触发项 -
Enter/Space:区分场景——是可展开项就 togglearia-expanded,否则执行跳转或回调 -
Esc:逐级收起,最后把焦点放回最外层触发按钮(不是丢给document.body)
移动端和 focusout 的坑怎么绕开
想点空白处关闭菜单?别只监听 document.addEventListener('click', ...)——点击菜单自己也会冒泡触发,导致刚展开就关闭。正确做法是:
立即学习“前端免费学习笔记(深入)”;
- 监听
mousedown(不是click),检查event.target是否在菜单容器及其触发按钮之外 - 同时监听触发按钮的
focusout,但加setTimeout(..., 100)延迟关闭(防 Chrome 快速切换 input 导致误关) - Firefox 中
focusout不冒泡,blur又不触发,单靠一个事件必翻车 - iOS Safari 需额外监听
touchstart,避免 300ms 延迟导致点击无响应
最常被忽略的一点:子菜单的 z-index 必须严格逐级递增,否则在嵌套较深时,上层菜单会遮住下层;而这个层级关系,没法靠 CSS 自动推导,必须 JS 动态控制或手写固定值。


















