纯css响应式多级下拉菜单需按设备交互能力而非屏幕尺寸适配,用@media (hover: none)或(pointer: coarse)降级为:focus-within与data-open控制,每级须设position: relative和绝对定位,确保视觉连续性与无障碍支持。

纯 CSS 实现响应式多级下拉菜单,桌面端靠 :hover + :focus-within,移动端必须降级为点击展开——CSS 本身无法模拟点击状态切换,@media 只能控制显隐,不能接管交互逻辑。
为什么 @media (hover: none) 比 @media (max-width: 768px) 更可靠
设备能力比屏幕尺寸更能决定交互方式。有些折叠屏(如 Galaxy Z Fold)横屏时宽度超 1000px,但 hover 不可用;而某些小屏 Windows 笔记本(1366×768)支持精细指针,hover 完全可用。
-
@media (hover: hover) and (pointer: fine)才启用.has-submenu:hover > .submenu规则 -
@media (hover: none), (pointer: coarse)下禁用所有:hover显隐逻辑,只保留:focus-within和 JS 控制的[data-open="true"] - 别在
@media (max-width: 767px)里写.submenu { display: block; }——这会让所有子菜单默认展开,失去控制权
:hover 和 :focus-within 必须同时写,且每级都要加 position: relative
只给一级加 position: relative,二级菜单会相对视口定位;没加 :focus-within,键盘用户 Tab 进入后菜单根本不显示。
- HTML 中每一级可展开项都要有
class="has-submenu",并确保子<ul class="submenu"></ul>是其直接子元素 - CSS 规则必须成对出现:
.has-submenu:hover > .submenu和.has-submenu:focus-within > .submenu - 子菜单自身也要设
position: absolute,且top: 100%(一级)、left: 100%(二级起),不能统一写top: 0; left: 0
鼠标移向子菜单时闪退?关键在视觉连续性,不是代码多酷
这是最常遇到的问题:鼠标从父菜单移向子菜单途中,因为两者不连续(有间隙),:hover 状态丢失,导致菜单瞬间收起。
- 确保父容器的
<li>包含子菜单区域,用padding或margin拉近距离,而不是靠绝对定位“悬空”子菜单 - 子菜单的
top值别设成100%,改用0并配合transform: translateY(100%) + transition更稳 -
li { position: relative; },li > ul { position: absolute; top: 100%; left: 0; opacity: 0; visibility: hidden; transition: opacity 0.2s, visibility 0.2s; },li:hover > ul, li:focus-within > ul { opacity: 1; visibility: visible; }
移动端点击展开必须用 data-open + CSS,别碰 display: none 的 DOM 切换
用 display: none 会导致屏幕阅读器跳过整个子菜单结构,且动画无法过渡。
- 移动端样式应基于
[data-open="true"]控制显隐,例如:.submenu[data-open="true"] { opacity: 1; visibility: visible; } - JS 只需切换
data-open属性值,不操作display、不重排 DOM - 务必为可展开项添加
role="button" aria-expanded,并同步更新aria-expanded值
复杂点在于:多级嵌套时,每级 data-open 都得独立管理,不能靠父级状态自动推导;而 :focus-within 在 Safari 旧版本(
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











