纯css多级响应式菜单的核心是语义化html结构与无障碍兼容性:必须用嵌套表达层级,正确添加role属性,用class而非:has()控制状态,以max-height实现动画,确保js失效或辅助技术启用时仍可操作。

纯 CSS 实现的多级响应式菜单,核心不是“怎么动”,而是“结构能不能被浏览器和辅助技术正确理解”——只要 HTML 嵌套语义正确、CSS 选择器精度够高、断点逻辑不依赖 JS,桌面悬停、移动端点击、键盘 Tab、屏幕阅读器都能自然适配。
HTML 必须用嵌套 <ul></ul> 表达父子关系
平铺的 <li> 无法表达层级,JS 或 CSS 都没法推导“Link B 是 Link 2 的子项”。浏览器和屏幕阅读器只认 DOM 树深度,不猜业务逻辑。
- 每个可展开项(如“产品”)必须是
<li class="has-submenu">,且其子<ul class="submenu"></ul>是该<li>的直接子元素,不能放在外面或用<div> 替代 <li>箭头符号(▼ / ▶)只能用伪元素 <code>::after添加,不能写死在 HTML 里,否则屏幕阅读器会读出“产品 ▼”,干扰语义 - 必须加
role="menu"到顶层<ul></ul>,每个可展开<li>加role="menuitem",子菜单<ul></ul>加role="menu",否则键盘↓→导航失效 - 现代方案:用
.menu-item.has-submenu.active > .submenu { display: block; },靠 class 控制状态,:has()仅作增强 - 别写
.menu-item.active ul——这会让所有深层子菜单一起弹出,失去层级控制 - 移动端点击展开时,JS 只需切换
.activeclass,不用操作display或visibility,让 CSS 全权负责显隐逻辑 - 用
vw或ch单位做弹性断点,例如@media (max-width: 60ch),比像素值更能反映文字流宽度 - 隐藏菜单项时,用
visibility: hidden; position: absolute;而非display: none,否则键盘 Tab 会跳过这些项,破坏焦点流 - 子菜单宽度别写死:
min-width: fit-content; max-width: 90vw; width: max-content;组合,防英文长词撑爆视口,也避免小屏缩成一团 - 用
max-height+overflow: hidden实现展开收起动画,例如.submenu { max-height: 0; overflow: hidden; transition: max-height 0.25s ease; } - 激活时设
.active > .submenu { max-height: 300px; },数值略大于最大可能子项总高;若动态增减,改用 JS 计算并写入style.maxHeight - 别用
opacity单独控制显隐——透明元素仍可聚焦、仍响应点击,必须配合pointer-events: none和aria-hidden="true"
:has() 控制多级显示,但得兼容旧浏览器
:has() 能精准匹配“有子菜单且当前激活”的项,比如 .menu-item:has(> .submenu).active > .submenu,但 Safari 15.4 及更早、所有 IE 版本都不支持。直接用会断层。
响应式断点要兼顾内容而非固定像素
按 max-width: 768px 切换汉堡菜单太粗糙:平板横屏 1024px 也可能需要折叠,小屏竖屏 375px 却可能容得下 4 个主项。关键看内容密度,不是设备型号。
动画必须避开 display 和 visibility
display: none 无法过渡,visibility: hidden 仍占布局空间——会导致点击区域偏移、Tab 焦点错位、滚动条意外出现。
真正难的从来不是让菜单“看起来像三级”,而是确保它在 JS 加载失败、屏幕阅读器启用、用户把字号放大到 200%、甚至网络只剩 3G 时,依然能被识别、抵达、操作——这些全藏在 <ul></ul> 嵌套顺序、role 属性位置、max-height 的取值精度里,而不是动画时长或图标旋转角度中。











