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

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











