多级菜单html结构必须用嵌套,子须为父的直接子元素;需添加role="menubar"、role="menuitem"、aria-haspopup="true"和aria-expanded="false"等语义属性,确保可访问性与键盘导航正常。

多级菜单的 HTML 结构怎么写才不踩坑
直接用嵌套 <ul></ul> + <li> 是最稳妥的起点,语义清晰、SEO 友好、屏幕阅读器也认得。别一上来就套 div 堆 DOM,后期加 JS 展开/收起、键盘导航(Tab/方向键)都会变麻烦。
关键点:
- 每级子菜单必须包在父级
<li>内,不能甩到外面——否则 CSS 定位和 JS 事件委托容易错位 - 用
aria-haspopup="true"和aria-expanded="false"标记可展开项,辅助技术才能正确识别状态 - 第一级
<ul></ul>加role="menubar",内部链接加role="menuitem",这是 ARIA 规范要求,不是可选项
纯 CSS 实现 hover 展开,但要注意兼容性断点
用 :hover 控制 display 或 visibility 最省事,但 iOS Safari 和部分安卓 WebView 对 hover 支持极弱——手指悬停不触发,点一下就跳转,二级菜单根本没机会露出来。
实操建议:
- 桌面端用
:hover+opacity+transform做淡入+下滑动画,视觉更稳 - 移动端必须配合 JS 切换
aria-expanded和 class,比如点击时给父<li>加is-open类,再用.is-open > ul显式显示子菜单 - 别依赖
pointer: fine媒体查询判断设备类型——它不可靠,优先用@media (hover: hover) and (pointer: fine)组合判断
JavaScript 控制展开逻辑,focusout 事件最容易漏
键盘用户按 Tab 进入菜单后,用方向键切换、Enter 确认,Esc 关闭——这些都好办。真正容易出问题的是:鼠标点空白处收起菜单时,blur 不可靠,focusout 才是正解。
常见错误现象:blur 在父元素上监听,结果点子菜单项时先触发 blur 再触发 click,菜单闪退;或者点其他页面元素,菜单没关。
正确做法:
- 监听整个菜单容器的
focusout,检查event.relatedTarget是否还在菜单内,不在才收起 - 用
tabindex="-1"给子菜单<ul></ul>赋予焦点能力,否则键盘无法进入二级项 - 每次展开前,把焦点移到第一个子项;收起时,把焦点还给触发它的按钮或链接
移动端点击穿透和 z-index 冲突怎么破
真机调试时经常发现:点开二级菜单后,手指稍微偏一点,就直接触发了下层页面的按钮——这不是 JS 问题,是 CSS 的 pointer-events 和层级没管好。
必须检查:
- 遮罩层(如果有)要设
pointer-events: auto,且z-index高于所有页面内容,但低于菜单本身 - 子菜单
<ul></ul>的position必须是absolute或fixed,且父<li>设position: relative,否则定位飘移 - 别用
z-index: 9999这种魔数,统一用 CSS 自定义属性管理层级,比如--z-menu: 100、--z-menu-sub: 110
多级菜单真正的复杂点不在结构,而在状态同步:JS 展开、键盘焦点、CSS 动画、ARIA 属性这四者必须严格对齐,漏掉任意一个,残障用户或窄带环境就卡住。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











