<nav>必须包裹至少两个真实链接且用<ul><li>结构,禁止单链接或<div class="nav">替代,否则语义失效、屏幕阅读器忽略、键盘导航跳过;须配aria-label和aria-current="page"。

必须用 <nav> 包裹完整导航结构,且内部至少包含两个真实 <a> 链接;单个链接、空 <nav> 或混入按钮/表单,语义即失效。
为什么不能只包一个 <a> 或用 <div class="nav"> 替代
浏览器和屏幕阅读器把 <nav> 当作“可跳转的导航集合”来识别,不是“带样式的容器”。单个链接不构成“集合”,会被降级为普通段落;换成 <div> 后,Tab 键会直接跳过整个区域,VoiceOver 无法触发“跳转到导航”快捷指令。
- 验证方式:在 Chrome 控制台执行
document.querySelectorAll('nav a[href]'),结果必须 ≥2 且每个href值非空(允许#或javascript:void(0)) - 常见错误:
<nav><a href="#">首页</a></nav>—— 即使视觉上像菜单,辅助技术也忽略它 - IE11 及部分旧读屏器要求显式声明
role="navigation",但现代标准优先用原生<nav>
<nav> 里该用 <ul> 还是直接放 <a>
必须用 <ul> + <li> 包裹所有 <a>,这是 HTML 规范强制要求,不是“推荐做法”。<ul> 的子元素只能是 <li>,否则浏览器会自动修正(可能丢节点或套多层 <div>),导致语义断裂和键盘焦点错乱。
- 正确:
<nav aria-label="主导航"><ul><li><a href="/">首页</a></li><li><a href="/about">关于</a></li></ul></nav></li> <li>错误:<code><nav><a>首页</a><a>关于</a></nav>
—— 浏览器可能渲染为两个孤立<p>,读屏器播报“首页、关于”,无“列表”上下文 -
<li>必须每个只包一个<a>,禁止塞<button>或<form>—— 它们属于操作,不是导航目标
aria-label 和 aria-current 是硬性需求,不是可选项
没有 aria-label 或 aria-labelledby 的 <nav>,Lighthouse 无障碍检测直接报错“nav missing label”。纯图标菜单、汉堡展开栏、无可见标题的侧边栏,全靠这个属性告诉读屏器“这是什么导航”。
- 当前页必须加
aria-current="page",例如:<a href="/products" aria-current="page">产品</a>,否则键盘用户无法感知位置 - 避免用
title属性替代aria-label——title不被 VoiceOver 默认朗读,且仅鼠标悬停触发 - 同一页面多个
<nav>时,label 必须区分用途,如aria-label="主导航"和aria-label="相关文章导航",不能都写“导航”
Flex 布局下 <li> 宽度塌陷是高频坑
<nav> 本身无样式,但用 display: flex 布局 <ul> 时,<li> 默认不占宽,所有链接挤成一团,移动端点不准、hover 热区消失。
- 修复方式:给
<li>加flex: 1或min-width: max-content,确保每个项有独立可点击区域 - IE11 兼容注意:它对
flex下的<li>渲染异常,稳妥方案是改用display: inline-block+vertical-align: top - 别在
<a>上直接写 hover 样式 —— 应作用于<li>,否则焦点状态(:focus)和悬停状态不一致,违反 WCAG 2.4.7
真正决定语义是否生效的,从来不是标签写了没,而是 DOM 里有没有真实、可访问、结构合规的 <a href> 节点,以及它们是否被正确关联到导航意图中。上线前务必用 NVDA + Firefox 或 VoiceOver + Safari 实测朗读流程,而不是只看页面长得像不像导航栏。


















