必须用嵌套<ul>+<li>,子菜单作为父<li>的直接子元素;否则会导致键盘导航失效、屏幕阅读器朗读错乱、aria-expanded同步失败。

必须用嵌套 <ul> + <li>,子菜单作为父 <li> 的直接子元素;任何脱离这个结构的写法(比如把子菜单丢到页面底部再用 CSS 定位)都会导致键盘导航失效、屏幕阅读器朗读错乱、aria-expanded 同步失败。
为什么不能把子菜单写成独立 <div class="submenu">
常见错误是这样写:
<li>设置</li> <div class="submenu"><a href="#">账号</a></div>
这看似能用 CSS 定位出来,但实际会出三类硬伤:
- Tab 键跳过整个子菜单——因为
<div>不是语义化列表项,浏览器不把它纳入 tab 顺序 -
aria-expanded无法绑定到正确节点:它该控制的是“父<li>是否展开”,而不是一个游离的<div> - 移动端点击穿透:iOS Safari 在
:hover失效后,若没 JS 补救,用户点一次根本打不开子菜单
<ul> 嵌套的合法写法与 class 命名惯例
正确结构只有一种:子 <ul> 必须是父 <li> 的最后一个子节点。推荐用 class="has-submenu" 标记可展开项,子菜单统一用 class="submenu":
立即学习“前端免费学习笔记(深入)”;
<ul class="nav">
<li><a href="#">首页</a></li>
<li class="has-submenu">
<a href="#">产品</a>
<ul class="submenu">
<li><a href="#">Web 版</a></li>
<li class="has-submenu">
<a href="#">移动版</a>
<ul class="submenu">
<li><a href="#">iOS</a></li>
<li><a href="#">Android</a></li>
</ul>
</li>
</ul>
</li>
</ul>
注意两点:
- 不要给所有
<li>都加class="has-submenu",只加在真正有子菜单的项上,否则 JS 或 CSS 逻辑会误触发 -
class="submenu"必须紧贴在父<a>后面,不能被其他标签(如<span>或注释)隔开,否则:checked ~ .submenu这类选择器会失效
纯 CSS 展开方案:用 <input type="checkbox"> 替代 :hover
:hover 在触摸设备上不可靠,且无法记忆状态;<input type="checkbox"> 是更稳妥的无 JS 方案:
- 每个可展开项前放一个隐藏
<input id="sub-1">,用<label for="sub-1">包裹文字,点击即切换勾选 - CSS 写
input[type="checkbox"]:checked ~ .submenu { display: block; },注意~要求.submenu是<input>的后续兄弟元素,所以 HTML 顺序必须是:<input>→<label>→<ul class="submenu"> - 别用
visibility: hidden或opacity: 0隐藏子菜单——它们仍占布局空间,且焦点无法进入其中的链接
缩进与层级视觉区分的关键细节
用 padding-left 做缩进,别用 margin-left 或 position: absolute:
-
padding-left保证 tab 键焦点框位置准确,屏幕阅读器能按视觉层级朗读 - 不同级用不同
padding-left值(如一级1rem、二级2.5rem、三级4rem),配合border-left加色块,比单纯缩进更易识别 - 超过三级嵌套时,CSS 缩进容易重叠、屏幕阅读器可能跳过中间层——建议三级封顶,更深逻辑改用页面内锚点或独立页面承载
嵌套本身很简单,难的是让每层都对键盘、屏幕阅读器和触摸操作保持一致响应。结构一旦写错,后面加多少 JS 和 CSS 都是补漏,不是加固。



















