ul/li嵌套结构必须严格闭合在父级内部,子菜单须为li的直接子节点;悬停热区需无缝衔接,消除像素级间隙;aria-haspopup必须为"menu"且配合role="menu"等语义属性;移动端应采用checkbox+visibility方案替代:hover。

ul/li嵌套结构必须严格闭合在父
子菜单
- 如果没被包在父级
- 标签里,浏览器会把它当全新列表处理,导致DOM层级断裂、CSS选择器失效、屏幕阅读器无法识别父子关系。最典型错误是把子
- 写成和父
- 同级,或者中间夹了空格、注释甚至。
实操建议:
- 每个带子菜单的
- 必须显式闭合,子
- 作为其**直接子节点**出现,不能靠JS动态插入到body或其他位置
- 检查开发者工具中,
ul.submenu是否确实是li.has-submenu的直接子元素(不是孙子、不是兄弟) - 避免使用
display: none隐藏子菜单——它会让辅助技术彻底忽略该节点;改用visibility: hidden+aria-hidden="true" - HTML中不要给所有
- 都加
class="has-submenu",只加在真正有ul.submenu子元素的项上,否则CSS或JS逻辑会误触发
:hover在三级菜单中容易中断的根源与修复
二级菜单能展开,三级却一碰就收,通常不是CSS写错了,而是悬停热区存在像素级断连。浏览器规范要求:hover必须持续覆盖鼠标路径,一旦父
- 底部和子
- 顶部之间有哪怕1px空隙,状态立即丢失。
- 子
- 用
- 下边缘,消除Y轴间隙
- 或给父
- 加
padding-bottom: 1px,扩展热区向下延伸 - 绝对定位时确保
left值与父 - 左边界完全对齐,注意
box-sizing和边框是否影响计算 - 三级菜单必须设
left: 100%+top: 0右向展开,不能依赖margin-left偏移
top: 100%+margin-top: -1px紧贴父aria-haspopup="menu"不是可选项,是强制要求
设成
aria-haspopup="true"在现代读屏器(如NVDA + Firefox)下会导致子菜单被跳过,W3C ARIA 1.1已废弃该值。语义错误会直接破坏键盘导航链路。实操建议:
- 一级触发项(如
li > a或button)必须加aria-haspopup="menu"和aria-expanded="false" - 对应子菜单容器(
ul)加role="menu"和aria-labelledby,值为触发元素的id - 禁用项加
aria-disabled="true",并确保tabindex="-1"且不响应键盘事件 - 不要同时给同一元素设
aria-haspopup和aria-controls——二者语义冲突
移动端必须放弃纯:hover,但别急着写完整JS
iOS Safari和Android Chrome真机上,
:hover仅在模拟桌面模式时生效,点击后不保留状态。纯CSS方案等于不可用,但也不意味着必须立刻引入框架或手写全套焦点管理。最小可行方案只需两步:
- HTML中为每个可展开项静态添加
class="has-submenu",并在其后紧跟<input type="checkbox" id="sub-1" hidden>和<label for="sub-1">...</label> - CSS用
input[type="checkbox"]:checked ~ .submenu { visibility: visible; }控制显示(注意是~,不是+,允许中间有label) - 关键:子菜单用
visibility: hidden而非display: none,保证Tab键能进入其中的a链接 - 不推荐用
opacity: 0或transform: scale(0)隐藏——它们仍占布局空间,且焦点不可达
role、aria-属性、DOM位置和CSS定位是否全部对齐。少一个position: relative,或漏掉一个aria-labelledby,键盘用户和读屏器就会卡在某一层进不去。
常见错误现象:
ul ul { top: 100%; margin-top: 2px; }——这2px就是闪退元凶。实操建议:
- 同级,或者中间夹了空格、注释甚至
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











