nav + ul 是唯一语义正确的导航结构,必须确保 nav 内仅一个 ul、li 直接子元素含 a、子菜单用 li > ul 嵌套,样式需重置 ul 默认边距和列表符号,并用 block/flex 使 a 可聚焦可 hover。

nav + ul 是唯一靠谱的 HTML 结构
别用 div 套 a,也别碰 table,语义错误会直接让屏幕阅读器读成“一行三个单元格”,而不是“首页、产品、关于”三个可跳转项。浏览器和 SEO 依赖 nav 标签识别导航意图,而 ul 是唯一合法容器——li 必须是它的直接子元素。
常见错误包括:nav 里塞多个 ul(应只放一个逻辑导航区块)、li 里没包 a(导致 Tab 键无法聚焦)、子菜单用 div 替代嵌套 ul(破坏层级语义)。
-
nav只包裹一个ul,别堆叠多个nav - 每个菜单项必须是
li>a,即使当前页也要保留href(可用#或实际路径) - 子菜单用
li>ul嵌套,层级建议 ≤ 2 级;三级需 JS 控制,否则 hover 易中断 - 分组标题不用
li,改用h3或带aria-disabled="true"的a
ul 和 a 的样式重置必须一步到位
浏览器给 ul 默认加了上下 margin 和左侧 padding-left,不处理就会顶部留白、文字缩进、圆点残留。这不是“不好看”,而是布局基准彻底跑偏。
a 默认是内联元素,不设 display: block 或 display: flex,hover 热区就只在文字上,左右空白点不动——用户会以为菜单失灵。
- 清空
ul:必须写全ul { margin: 0; padding: 0; list-style: none; },漏掉任一属性都可能出问题 -
a要撑满:用display: block+padding: 12px 20px控制尺寸,别设height(字体渲染高度不可控) - 悬停效果必须作用于
a,不是li;当前页高亮靠服务端或 JS 加class="active"到对应a - 分割线用
li:not(:last-child) { border-bottom: 1px solid #eee; },避免最后一项多一条线
:hover 展开子菜单时闪退?间隙是元凶
鼠标从父级 a 移向子 ul 时,经过两者之间的空白区域,:hover 状态立即中断,子菜单瞬间收起——这是纯 CSS 垂直菜单最典型的交互断裂。
iOS Safari 不支持 :hover,移动端点一下根本不会展开;而且键盘用户按 Tab 无法进入子项,screen reader 也读不到隐藏内容。
- 纯 CSS 方案仅适用于桌面端、且嵌套 ≤ 1 级的简单场景
- 要支持三级、键盘操作或移动端,必须用 JS,配合
aria-expanded和aria-hidden - 子菜单展开动画别只写
height,height: 0 → auto不触发 transition;改用max-height或transform: scaleY() - 如果坚持用
:hover,必须确保父a和子ul之间**无任何间隙**,常用技巧是设margin-bottom: -1px或用flex容器消除空白
折叠状态不能用 display: none
单纯写 display: none 会让整个导航区块从 DOM 可访问性树中消失:屏幕阅读器跳过它,键盘 Tab 流中断,SEO 也认为这部分内容不存在。
折叠不是删除,是“换种方式呈现”。侧边栏收起后,仍需保持结构完整、焦点可达、语义在线。
- 用
visibility: hidden+position: absolute+inset: 0 -300px 0 0把菜单移出视口,DOM 和 tabindex 全部保留 - 汉堡按钮必须带
aria-controls="nav-id",对应nav加id="nav-id"和role="navigation" - 展开后立刻把焦点移到第一个
a:document.querySelector("#nav-id a").focus(),否则键盘用户卡在按钮上 - 图标切换状态(如
fa-caret-down→fa-caret-up)必须同步更新aria-expanded值
复杂点不在怎么写,而在怎么“藏得合理”:可访问性不是附加功能,是垂直导航能被真实用户使用的底线。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











