<nav>必须包裹全部多级菜单DOM节点,包括触发按钮、各级ul/li/a及子菜单容器;aria-label需按功能唯一命名如“主导航”;子菜单须用ul+li+a结构;当前页仅用aria-current="page"标注在对应a标签上。

nav 必须包裹整个多级菜单区域,不能只包一级
多级下拉菜单是一个整体导航上下文,<nav> 作用是向辅助技术声明“这是一个独立的、功能明确的导航单元”。如果只把顶层 <ul> 套在 <nav> 里,而二级、三级菜单用 <div> 或 <ul> 单独散放,屏幕阅读器会认为它们是脱离导航的普通内容,键盘 Tab 流也会断裂。
正确做法是:所有层级(包括触发按钮、子菜单容器、所有链接)都必须位于同一 <nav> 内部。常见错误结构:<nav><ul>...</ul></nav><div class="submenu">...</div> —— 这等于把子菜单踢出了导航语义域。
要点:
-
<nav>开闭标签必须严格包裹从触发项到最深层<a>的全部 DOM 节点 - 子菜单容器(如二级
<ul>)无需额外role,它天然属于父<nav>的语义延伸 - 避免在
<nav>外部插入搜索框、登录按钮等非导航元素——它们不属于该导航上下文,应移出或另起<nav>
aria-label 必须按功能命名,不能写 "Navigation"
当页面存在多个 <nav>(比如顶部主导航 + 面包屑 + 页脚快捷入口),仅靠标签无法区分。辅助技术读出来全是“导航区域”,用户根本不知道当前聚焦的是哪个。
立即学习“前端免费学习笔记(深入)”;
写成 <nav aria-label="Navigation"> 或 <nav aria-label="Menu"> 是无效标注,等于没标。必须体现实际用途:
- 主导航用
<nav aria-label="主导航">(中文)或<nav aria-label="Main navigation">(英文) - 面包屑用
<nav aria-label="面包屑导航">,不是"Breadcrumb"或"Breadcrumbs"(拼写需统一) - 侧边栏工具菜单用
<nav aria-label="管理操作导航">,而非模糊的"Tools"
注意:aria-label 值必须唯一且可预测,不能动态生成(如加时间戳或随机 ID),否则语音识别引擎无法稳定映射。
子菜单结构必须用 ul + li + a,禁用 div 模拟
多级菜单的本质是一组有嵌套关系的跳转目标,<ul> 和 <li> 是唯一能向辅助技术传达“列表”“可计数”“层级隶属”的原生组合。用 <div> 堆砌链接,即使加了 role="list",也会被浏览器忽略或误读。
正确嵌套示例:
<nav aria-label="主导航">
<ul>
<li><a href="/home">首页</a></li>
<li>
<a href="/products">产品</a>
<ul>
<li><a href="/products/web">Web 工具</a></li>
<li><a href="/products/mobile">移动应用</a></li>
</ul>
</li>
</ul>
</nav>
关键约束:
- 每个可跳转项必须是
<a href="...">,哪怕当前页也得保留href(用于键盘聚焦和回车触发) - 禁用
<span>+ click 事件模拟链接,它不可聚焦、不可键盘访问 - 不要给
<ul>加role="list"或role="menu"—— 重复声明反而干扰默认行为
当前页标识只能用 aria-current="page",且只加在 a 上
导航中“当前所在页”不是禁用状态,而是语义终点。用 aria-disabled="true"、移除 href 或加 class="active" 都会导致键盘用户无法聚焦、读屏跳过、甚至误报“已禁用”。
唯一合规方式:保留 href,并在对应 <a> 上添加 aria-current="page":
- 值必须是
"page",不能是"true"、"active"或"current" - 只加在具体链接上,不能加在
<li>或<nav>上 - 若子菜单中某项为当前页,同样只标在那个
<a>上,父级链接不继承
复杂点在于:多级菜单中,用户可能同时处于“产品页”(一级当前)和“Web 工具页”(二级当前)。这时两个 <a> 都要各自加 aria-current="page",辅助技术会分别播报“产品,当前页”和“Web 工具,当前页”,这是预期行为。



















