多级下拉菜单需依赖html嵌套ul/li结构与css子选择器(>)精准控制层级,错误使用div或后代选择器会导致全展开或不显示;必须设li为relative定位,配合hover/focus-within/js切换active类,并确保无障碍语义。

多级下拉菜单不是靠“套 div 堆出来”的,而是靠 HTML 层级结构 + CSS 选择器语义精准匹配实现的。只要结构错一层、选择器用错一个符号,就会出现“点一级,全展开”或“子菜单不显示”这类问题。
HTML 必须用嵌套 ul/li,不能用 div 拼接
浏览器和辅助技术(如屏幕阅读器)只认标准列表语义。用 div 包菜单项,:hover 或 aria-expanded 都会失效,键盘导航也断掉。
- 正确结构:每个可展开项是
li,其子菜单是该li的直接子元素ul - 错误写法:
li里放div,再把子ul放在父ul外面——这会让li:hover > ul完全不生效 - 必须加
class="dropdown"或aria-haspopup="true"标识可展开项,否则无法被 JS 或无障碍工具识别
> 子选择器是隔离层级的关键,空格后代选择器会出事
.dropdown ul 这种写法会匹配所有深度的 ul,导致点击一级菜单时三级、四级内容全弹出来。真正要的是“只开当前这一层的直属子菜单”。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- ✅ 正确:
li.dropdown:hover > ul—— 只对鼠标悬停的li的**直接子ul** 生效 - ❌ 错误:
li.dropdown:hover ul—— 后代选择器,会穿透到所有嵌套ul,一展到底 - 同理,JS 切换类名后,CSS 也要配
li.active > ul,否则active类一加,整个树都展开
移动端必须用 click + focus-within,纯 :hover 不可靠
手机没有 hover 状态,且触摸目标小,容易误触。仅靠 CSS :hover 的菜单在 iOS 或 Android 上基本不可用。
- 方案一:JS 绑定
click,给目标li切换active类,再配合li.active > ul - 方案二:用
li:focus-within > ul支持键盘焦点展开(对屏幕阅读器友好),但需确保tabindex="0"加在可点击的a或button上 - 别用
pointer-events: none遮挡子菜单区域——它会同时禁掉触摸和焦点,导致子项完全不可操作
:has(ul) 能动态识别可展开项,但兼容性要查清
现代写法可以用 li:has(> ul) 自动给有子菜单的项加箭头或 padding,不用手动加 class="has-child"。但它在 Safari 15.4+ 和 Chrome 105+ 才稳定支持。
- 检查兼容性:用 caniuse.com/css-has 查当前项目支持范围
- 降级方案:保留
class="has-child",JS 初始化时遍历li:has(> ul)并补上该 class - 注意:
:has()不能出现在 CSS 规则左边以外的位置(比如不能写ul:has(li:hover)),否则整条规则会被浏览器忽略
最常被忽略的是:菜单容器的 position: relative 必须设在每层 li 上,而不是只设在外层 nav。否则子菜单的 position: absolute 会相对于页面顶部定位,飘走。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










