nav中二级菜单必须严格遵循语义层级:子菜单ul必须是li的直接子元素,且触发li需设position: relative以定位绝对子菜单;CSS显隐应避免display: none以保障无障碍访问。

nav 里嵌套 ul 的层级必须严格对应语义
不是所有 <nav> 都能自然撑起二级菜单逻辑,关键在 <ul> 嵌套是否形成“父项包含子项”的 DOM 层级。浏览器不认“看起来像菜单”的结构,只认 <li> 里直接包裹 <ul> 这一事实。
常见错误是把子菜单写在 <li> 外面,比如:
<nav>
<ul>
<li>产品</li>
<ul class="submenu"><li>硬件</li></ul>
</ul>
</nav>这样 .submenu 和“产品” <li> 没有父子关系,li:hover .submenu 根本选不到——CSS 选择器不会跨兄弟节点匹配。
正确结构必须是:
立即学习“前端免费学习笔记(深入)”;
<nav>
<ul class="primary-menu">
<li>
<a href="#">产品</a>
<ul class="submenu">
<li><a href="/hardware">硬件</a></li>
<li><a href="/software">软件</a></li>
</ul>
</li>
</ul>
</nav>-
<nav>是语义容器,不参与定位或显隐控制 - 一级
<ul>是主菜单流式布局基础,通常设display: flex或float: left - 每个可展开的
<li>必须同时包含<a>和其直属<ul class="submenu"> - 子菜单
<ul>必须是该<li>的**直接子元素**,不能隔层(如套了<div>)
position: relative 必须加在触发 hover 的 li 上
子菜单用 position: absolute 定位,但它的参考系是谁?不是 <nav>,也不是外层 <ul>,而是它最近的、设置了 position: relative 的祖先 —— 也就是那个带子菜单的 <li>。
如果漏掉这句,.submenu 会相对于整个页面或最近的定位祖先偏移,导致错位甚至飞出视口。
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
典型写法:
.primary-menu li {
position: relative; /* 关键!没有它,top: 100% 就失效 */
}
.submenu {
position: absolute;
top: 100%;
left: 0;
display: none;
}
.primary-menu li:hover .submenu {
display: block;
}- 不要给
<nav>或外层<ul>加position: relative—— 它们只是容器,不是定位锚点 - 如果用了
flex布局一级菜单,li默认不占文档流高度,加position: relative不影响布局 - 移动端若用
:focus-within替代:hover,同样依赖这个position: relative锚点
display: none 和 visibility: hidden 的行为差异直接影响键盘导航
纯 CSS 二级菜单若只靠 display: none 控制显隐,在键盘 Tab 导航时会出现两个问题:子菜单项无法被聚焦、屏幕阅读器读不到隐藏内容。
这不是“能不能用”的问题,而是“要不要支持无障碍”的分水岭。
更稳妥的做法是保留 DOM 可访问性,仅视觉隐藏:
.submenu {
position: absolute;
clip-path: inset(100%); /* 推荐:完全隐藏且不影响焦点流 */
/* 或者用传统组合: */
/* position: absolute; */
/* opacity: 0; */
/* pointer-events: none; */
/* transition: opacity 0.2s; */
}-
display: none会让元素彻底退出渲染树和焦点流,Tab 键跳过整个子菜单 -
visibility: hidden保留占位,可能撑开父容器高度,破坏纵向菜单紧凑感 -
clip-path: inset(100%)是现代方案:视觉不可见、仍可聚焦、不影响布局 - 若用
opacity + pointer-events,记得加transition,否则 hover 切换生硬
纵向菜单中 float 和 flex 的兼容性取舍
纵向二级菜单常被误认为“必须用 float”,其实 flex 更干净,但要注意老版本 Safari 对 flex-direction: column 下 li:hover > ul 的支持不稳定。
当前(2026 年)推荐优先用 flex,但需规避一个坑:
.primary-menu {
display: flex;
flex-direction: column; /* 纵向排列 */
}
.primary-menu li {
position: relative;
}
/* ❌ 错误:flex 容器下,:hover 直接子 ul 不会自动宽度继承 */
.primary-menu li:hover > ul {
width: 100%; /* 必须显式设置,否则 collapse */
}- 用
float时,一级<li>需要clear: both防止浮动塌陷,代码更啰嗦 - 用
flex时,子菜单<ul>默认宽度为 0,必须设width: 100%或min-width: max-content - IE11 不支持
flex-direction: column下的 hover 子选择器,如需兼容,退回float方案
实际做纵向二级菜单时,最易被忽略的是子菜单的宽度继承逻辑和键盘焦点路径 —— 这两点不处理,菜单在真实用户场景里基本等于不可用。


















