HTML下拉菜单必须用ul/li嵌套结构并添加ARIA角色;CSS hover需消除悬停间隙避免闪退;移动端须用JS切换class替代:hover;键盘导航需配合visibility隐藏、tabindex控制及焦点管理。
多级下拉菜单的可访问性和CSS控制都依赖标准列表语义。浏览器对ul和li的默认缩进、焦点流、屏幕阅读器播报都有成熟支持。用div模拟菜单会导致键盘导航失效、Tab跳转错乱,且:hover在移动端无意义时更难降级。
正确结构要点:
li的直接子元素,且为ul(不是ol或div)a标签必须包裹在li内,不能让a直接作为ul子元素ul建议加role="menubar",子ul加role="menu",a加role="menuitem"
视觉上相邻的父项与子菜单之间一旦存在像素级空隙,鼠标移出时:hover状态立即丢失,子菜单闪退。这不是bug,是CSS规范行为。
解决方法只有两个方向:
立即学习“前端免费学习笔记(深入)”;
top: 100% + margin-top: -1px把子ul紧贴父li底部,消除Y轴间隙li设置padding-bottom: 1px(或类似微调),扩展热区向下延伸ul时,确保其left值与父li边界完全对齐,避免因box-sizing或边框导致偏移错误示例:ul ul { top: 100%; margin-top: 2px; } —— 这2px就是闪退元凶。
iOS Safari 和 Android Chrome 对:hover的支持极其有限:仅在模拟桌面模式时生效,真机点击后不保留悬停态,且无法触发子菜单展开。纯CSS方案在移动设备上等于不可用。
最小可行JS方案(不依赖框架):
document.querySelectorAll('nav li.has-submenu > a').forEach(el => { el.addEventListener('click', e => { e.preventDefault(); const parentLi = el.parentElement; parentLi.classList.toggle('submenu-open'); });});
对应CSS只需监听.submenu-open > ul而非:hover,并确保has-submenu类已静态写入HTML。
注意:e.preventDefault()必须加,否则页面会跳转;同时要避免在桌面端重复绑定导致双击才展开。
仅靠:hover和display: block会让Tab键跳过所有子菜单项,用户无法用键盘操作二级选项。这是WCAG 2.1 2.1.1关键失败点。
必须配合以下动作:
ul默认设visibility: hidden; position: absolute;,而非display: none(后者会从焦点流中彻底移除)tabindex="-1"手动控制子菜单项的可聚焦性,在展开时动态设tabindex="0",收起时重置ArrowDown/ArrowUp在同级菜单间切换,Escape收起当前展开项最易被忽略的是:当子菜单展开后,焦点仍停留在父链接上,用户按Tab会跳到下一个顶级项,而不是第一个子项——这需要JS主动focus()到子菜单首个a。