纯CSS多级下拉菜单需满足三条件:父级li设position:relative、子菜单用display:none/block切换、:hover选择器链完整嵌套;否则二级不显、三级消失。
纯 CSS 多级下拉菜单在桌面端可行,但必须满足三个硬性条件:父级 li 显式设 position: relative、子菜单用 display: block(不能靠 visibility 或 opacity 模拟隐藏)、:hover 选择器链必须完整嵌套。缺一不可,否则二级就展不开,三级直接消失。
position: relative 和 display 切换最常见错误是只给 .dropdown-menu 加 position: absolute,却忘了它的直接父级 li 没设 position: relative——结果子菜单相对于 body 定位,飘到页面左上角,肉眼看不见。
另一个高频问题是子菜单默认用了 visibility: hidden 或 opacity: 0,然后指望 :hover 时只改 visibility: visible。这不行::hover 不会“唤醒”被 visibility 隐藏的元素的交互区域,鼠标移过去根本触不到,状态立刻中断。
display: none,触发时显式切为 display: block(或 grid/flex)li(比如带子菜单的项)都得加 position: relative
float: left 布局一级导航——它不创建层叠上下文,z-index 会失效;改用 display: flex + 显式 relative
:hover 选择器链怎么写才不丢级?浏览器不会“穿透”多层 DOM 自动维持 hover 状态。你必须把每一级的触发关系写死,少一个环节,下一级就卡住。
立即学习“前端免费学习笔记(深入)”;
比如三级菜单,不能只写 .menu > li:hover .submenu —— 这会匹配所有后代,导致鼠标一移开一级,三级还挂着;也不能漏掉中间层,写成 .menu > li:hover > ul > li:hover > ul 才对。
.first-level-menu > li:hover > .second-level-menu
.second-level-menu > li:hover > .third-level-menu
.menu li:hover ul,它会污染非目标子菜单<li class="has-sub"><a>Item</a><ul>...</ul></li>,则选器应为 .has-sub:hover > ul,不是 li:hover ul
这是因为一级 li 和二级 ul 之间有像素级空隙,鼠标经过时 :hover 状态丢失,子菜单瞬间 display: none。
解决思路不是加 delay(CSS 无可靠 hover delay),而是物理缝合触发区域:
top: 100% 后,再加 margin-top: -1px 或 padding-bottom: 2px 到一级 li,把空隙填满left: 100% + top: -1px 对齐二级项顶部,避免垂直偏移产生新空隙min-width: 100%,防止内容窄导致鼠标滑出触发区white-space: nowrap 在子菜单链接上,否则长文本溢出会撑宽容器,反而制造新断点:hover 在 iOS Safari 和 Android Chrome 上仅支持单次模拟悬停(比如长按),且不维持状态。所谓“响应式纯 CSS 菜单”,真机测试基本二级都打不开。
无障碍更致命::hover 和键盘 Tab 焦点完全无关。屏幕阅读器用户按 Tab 进入一级菜单,二级内容根本不会渲染,也读不出来。
@media (max-width: 767px))直接 display: none 所有子菜单,别试图用 :focus-within 模拟——iOS 对它的支持不稳定<input type="checkbox" id="menu-toggle"> + label[for] 实现点击开关,配合 ~ 选择器控制显示,零 JS 也能保基本可用性tabindex="0" 和 aria-expanded,并用 JS 监听 click 和 keydown(Enter/Space)切换状态最常被忽略的一点:哪怕只做桌面端,也要给父容器设 z-index。Navbar、Modal、Toast 都有预设层级,没显式声明的子菜单大概率被盖住,连开发者工具里都看不到渲染异常——它就在那儿,只是被压在底下。