必须。三级菜单必须用嵌套<ul>,且子<ul>必须作为父<li>的直接子元素,否则CSS选择器失效、屏幕阅读器无法识别层级、Tab键导航断裂、移动端touch事件错位,语义错误导致功能缺失而非仅样式问题。

三级菜单必须用嵌套 <ul> 吗?
必须。不是“推荐”,是结构底线。用 <div> 套菜单,li:hover > ul 这类关键选择器会直接失效;屏幕阅读器读不出层级,Tab 键在子菜单间跳转会断裂;移动端 touch 事件也容易错位。语义错误带来的问题远比样式难调更致命。
正确写法只有一条铁律:子 <ul> 必须作为父级 <li> 的直接子元素。不能塞进 <a> 里,不能放在父 <li> 外面,也不能用 <span> 包一层再放 <ul>。
-
<li class="has-sub">服务<ul><li>咨询</li></ul></li>✅ -
<li class="has-sub"><a href="#">服务</a><ul><li>咨询</li></ul></li>✅(<ul>仍在<li>内) -
<li class="has-sub"><a href="#">服务<ul><li>咨询</li></ul></a></li>❌(<ul>在<a>内,DOM 层级断裂) -
<li class="has-sub">服务</li><ul><li>咨询</li></ul>❌(<ul>脱离父<li>)
li:hover > ul 在手机上为什么没反应?
因为 hover 是桌面行为,触摸屏没有“悬停”状态。纯 CSS 的 li:hover > ul 在 iOS/Android 上完全不触发——这不是 bug,是规范行为。
解决方案分两层:
立即学习“前端免费学习笔记(深入)”;
- 临时补救:用
:focus-within模拟(需给<a>加tabindex="0"),但 Android Chrome 旧版支持差,且首次点击不聚焦 - 可靠做法:加轻量 JS,监听
click,切换.is-open类,CSS 控制显隐。不依赖 hover,也不需要 jQuery - 必须同步处理键盘:按
Enter或Space也要展开,Escape收起,否则无障碍失败
示例 JS 片段(仅核心逻辑):
document.querySelectorAll('.has-sub > a').forEach(el => {
el.addEventListener('click', e => {
e.preventDefault();
const parent = el.parentElement;
parent.classList.toggle('is-open');
});
});
三级菜单定位总偏移、遮不住或闪退怎么办?
根本原因不是动画没写好,是 position 链路断了。常见三处断裂点:
- 父级
<li>没设position: relative,子<ul>的left: 100%就相对整个页面定位 - 二级菜单用了
left: 100%(对齐父右边缘),但三级还沿用这个值,导致越级偏移累积 - 隐藏用
display: none,动画过渡会失效;用opacity: 0; visibility: hidden; pointer-events: none才能兼顾动画与交互阻断
CSS 关键片段:
.has-sub > ul {
position: absolute;
top: 100%;
left: 0; /* 二级、三级都用 left: 0,靠父级 relative 定位 */
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.2s, visibility 0.2s;
}
.has-sub.is-open > ul,
.has-sub:focus-within > ul {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.has-sub > ul > li > ul {
top: 0;
left: 100%; /* 三级才右对齐二级项 */
}
ARIA 属性漏加会出什么实际问题?
不是“锦上添花”,是功能缺失。漏掉 aria-haspopup="true" 和 aria-expanded="false",NVDA/JAWS 等读屏软件会把可展开项当成普通链接,用户完全不知道有下级菜单;漏掉 role="menu" 和 role="menuitem",键盘导航时 Arrow 键无法在菜单项间移动。
必须配对出现:
- 有子菜单的
<li>:加aria-haspopup="true"+aria-expanded="false" - JS 切换展开状态时,同步更新
aria-expanded值为"true"或"false" - 最外层
<ul>加role="menubar",每个一级<li>加role="menuitem",子<ul>加role="menu"
不加这些,菜单对视障用户就是不可用的——这不是体验问题,是合规红线。
嵌套层级本身不难,难的是每层 DOM、CSS、JS、ARIA 四者咬合严丝合缝。少一个 position: relative,或漏一次 aria-expanded 同步,整条链就断在真实用户手里。



















