<nav>标签仅用于包裹同时满足“全局性、重复性、主要跳转路径”三条件的链接集合,必须用<ul><li>结构组织,多个<nav>须配aria-label区分,且禁止嵌套或置于<header>/<footer>内。

<nav> 标签不是“所有链接的收纳盒”,它只该包裹真正承担站点级跳转路径的链接集合;辅助类链接(如返回顶部、打印、分享)塞进去反而会干扰屏幕阅读器识别主干导航。
哪些链接必须放进 <nav> 才算语义正确
只有同时满足「全局性」「重复性」「主要跳转路径」三个条件的链接组,才配用 <nav>:
- 顶部主导航栏(
首页、产品、定价、联系)——全站每页都出现,且是用户跨页面的核心路径 - 侧边文档目录(含真实
href="#section1"锚点链接)——结构稳定、可跳转、服务于内容浏览流 - 页脚快捷入口(
隐私政策、服务条款、帮助中心)——非装饰性,有独立 URL,且在多页复用
不满足任一条件的,比如单个「返回顶部」链接、带 onclick="print()" 的按钮、或仅用于当前页内跳转的「目录索引」,都不该进 <nav>。
<nav> 里为什么必须用 <ul><li> 包裹链接
屏幕阅读器不靠 <nav> 标签名识别“这是一组导航项”,而是依赖 <ul> 的列表语义来判断项数、顺序和层级。平铺写 <nav><a href="/home">首页</a><a href="/about">关于</a></nav>,AT 可能将其读作连续文本,而非可遍历的导航集合。
- 正确写法:
<nav aria-label="主导航"><ul><li><a href="/home">首页</a></li><li><a href="/about">关于</a></li></ul></nav> - 错误写法:
<nav><a href="/home">首页</a><a href="/about">关于</a></nav>(无结构,语义断裂) - 更糟写法:
<nav><div><a href="/home">首页</a></div><div><a href="/about">关于</a></div></nav>(<div>无语义,AT 无法归纳为列表)
多个 <nav> 共存时,aria-label 不是可选项
浏览器和屏幕阅读器不会自动区分页面里的多个 <nav>。如果不加 aria-label,它们只会统一报“导航”,用户根本不知道哪个是主导航、哪个是页脚链接、哪个是面包屑(哪怕你用了 <ol class="breadcrumb">)。
- 主导航:
<nav aria-label="主导航"> - 页脚快捷链接:
<nav aria-label="页脚导航"> - 面包屑(不推荐用
<nav>,但若用):<nav aria-label="当前位置"> - 绝对禁止:
<nav>套<nav>—— HTML 规范明令禁止嵌套
另外,<nav> 必须直接位于 <body> 下,不能嵌在 <header> 或 <footer> 里——否则语义被稀释,部分 AT 会忽略。
SPA 场景下,<nav> 的 DOM 就绪比样式更重要
React/Vue 中常见问题:初始渲染时 <nav> 内为空,等 API 返回菜单数据后再动态插入链接。结果是页面加载完成时,document.querySelector('nav a') 查不到任何有效节点,屏幕阅读器直接跳过整个区域。
- 解决办法:预置占位链接(如
<a href="/">首页</a>),再用 JS 替换其余项 - 路由切换后,必须同步更新当前页链接的
aria-current="page"属性(不是仅加 CSS class) - 伪链接无效:
<button onclick="goTo('about')">关于</button>或<a href="#" onclick="...">都不算语义化导航,AT 无法识别为可跳转目标
最常被忽略的细节:很多团队花时间做下拉动画和视觉高亮,却没在 DOM 层面确保 <nav> 里始终有真实、可访问、结构清晰的链接节点——语义化不是“写了标签就完事”,而是“每一帧都有意义”。

















