纯CSS响应式多级菜单必须同时使用:hover和:focus-within,否则桌面悬停失效、移动端点击无响应、键盘不可用;需为每级菜单精准声明选择器、设置定位上下文、添加tabindex="0",并配合data-open由JS控制降级逻辑。

纯 CSS 实现响应式多级菜单,必须同时用 :hover 和 :focus-within,否则桌面端悬停失效、移动端点击无响应、键盘用户完全不可用——这不是兼容性补丁,而是基础可访问性要求。
为什么只写 :hover 在现代设备上必然失败
浏览器规范明确:触屏设备(iOS Safari、Android Chrome)把首次点击解释为“模拟 hover”,第二次才触发链接;:hover 本身不产生可编程状态,无法被 JS 监听或切换。更关键的是,:focus-within 在 Safari 15.4+ 和所有现代浏览器中已稳定支持,它才是唯一能同时覆盖“鼠标悬停”“触屏点击”“键盘 Tab 进入子项”三类行为的纯 CSS 方案。
- 别信
@media (hover: hover)能拯救移动端——它只判断设备能力,不解决用户想点开却点不开的问题 - 不要给
.submenu初始设visibility: hidden就以为安全:它会让鼠标移入时因交互区域丢失而闪退 - 所有可展开的
<li>必须加tabindex="0",否则:focus-within在触屏点击时不会触发(iOS Safari 需显式聚焦才能激活)
:hover 和 :focus-within 必须成对出现且层级精准
写错选择器链,二级菜单就永远不显示。浏览器不会“自动延续” hover 状态,每一级都得手动声明触发关系。
- 一级菜单:
.has-submenu:hover > .submenu, .has-submenu:focus-within > .submenu - 二级菜单:
.submenu > li.has-submenu:hover > .submenu, .submenu > li.has-submenu:focus-within > .submenu - 父级
<li>必须设position: relative,否则position: absolute的子菜单会相对 body 定位,飘出视口 - 禁止使用宽泛选择器如
li:hover ul——它会匹配所有后代,导致鼠标移开一级后三级菜单还挂着
移动端降级不是“隐藏 hover”,而是彻底禁用并交由 JS 控制 data-open
CSS 媒体查询不能接管交互逻辑,@media (hover: none) 下所有 :hover 规则自动失效。此时若不引入 JS,菜单将完全不可展开。
- 在
@media (hover: none), (pointer: coarse)中,移除全部:hover显隐规则,只保留:focus-within和[data-open="true"]的样式 - JS 只做一件事:
el.dataset.open = el.dataset.open === "true" ? "false" : "true",不操作 class、不修改 display - CSS 控制显隐:
[data-open="true"] .submenu { visibility: visible; opacity: 1; transform: translateX(0); },初始状态必须是visibility: hidden; opacity: 0; transform: translateX(-10px) - 绝对禁止在媒体查询里写
.submenu { display: block !important }——它会覆盖 JS 的data-open控制,导致无法收起
定位错位、z-index 失效、闪退这三类问题,90% 源于忽略定位上下文
不是 CSS 写得不够“炫”,而是没守住最底层约束:每级子菜单的 position: absolute 必须基于带 position: relative 的直接父 <li>,而不是外层 <ul></ul> 或 <nav></nav>。
- 一级子菜单:
top: 100%; left: 0; - 二级起统一用:
left: 100%; top: -1px;(-1px 是为了对齐上一级边框,填掉悬停断点) - 父容器若含
overflow: hidden,子菜单会被裁剪——改成overflow: visible或用clip-path: none -
z-index必须设在定位元素上:只给.submenu设z-index: 101,别给未定位的<nav></nav>设z-index: 100,否则无效
最易被忽略的一点:所有 .has-submenu 元素必须是 <li>,且其 <ul class="submenu"></ul> 是它的**直接子元素**。DOM 结构错一层,CSS 选择器就全盘失效,再调 transition-delay 或 transform 都救不回来。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











