根本原因是被嵌套在语义断层容器(如或role="group"的)中导致地标传播中断,且内部缺乏可聚焦元素;必须唯一且为直系子元素,否则屏幕阅读器无法定位核心内容。

为什么写了 <nav></nav> 屏幕阅读器还是跳不过去
根本原因不是样式隐藏或 JS 干扰,而是 <nav></nav> 被写在了语义“断层”位置:比如包在 <table>、<code><ul></ul> 或带 role="group" 的 <div> 里——这些父容器会中断地标传播,浏览器可能直接丢弃该节点。更常见的是 <code><nav></nav> 里只放纯文本或没加 tabindex="0" 的容器,导致内部无可聚焦元素,读屏器判定为“不可交互”,跳过不读。
必须确保:
-
<nav></nav>是的直接子元素,或至少不被语义受限标签(如<table>、<code><ol></ol>)包裹 - 内部至少有一个可聚焦元素:
<a href></a>、<button></button>,或带tabindex="0"的容器 - 多个
<nav></nav>必须配aria-label,且值要具体,如aria-label="主导航",不能是泛泛的"Navigation" -
直接包含<header></header>、<main></main>、<footer></footer>(并列) -
<main></main>内部不需要再套一层<section></section>——除非该<section></section>有独立标题且需参与大纲生成;否则<main><h1>...</h1></main>更干净 - 如果框架强制输出嵌套 DOM(如 Next.js 的
<main></main>包在<div> 里),必须用 CSS 或 JS 在运行时修正,确保最终渲染的 <code><main></main>是的直系子节点导航项必须用
<ul></ul>或<ol></ol>,别用<div> 堆砌 <p>导航的本质是一组可顺序访问的跳转目标。<code><ul></ul>或<ol></ol>不仅传达“列表”语义,还自带隐式可计数、可遍历能力。用<div> 套链接,哪怕加了 <code>role="list",也会丢失键盘方向键导航、读屏器报数(“第1项,第2项”)等原生行为。选
<ul></ul>还是<ol></ol>?- 主导航、页脚链接等无强顺序关系 → 用
<ul></ul> - 面包屑、步骤条、章节锚点等有明确层级或顺序 → 用
<ol></ol>,读屏器会播报序号 - 当前页链接必须保留
href(哪怕指向当前 URL),并加aria-current="page";禁用项用disabled或aria-disabled="true"+tabindex="-1",不能删href
验证路标是否真实生效,别只看标签名
DevTools 里看到
<nav></nav>和<main></main>标签,不代表它们被辅助技术识别为地标。真正起作用的是浏览器暴露给读屏器的 ARIA role、name 和 level。实操验证方式:
- Chrome DevTools → Accessibility 面板 → 展开 “Landmarks” 节点,看实际识别出的地标结构
- NVDA 用户按
Insert+Tab切换地标,确认“主导航”“主内容”是否出现且名称准确 - 用 axe 浏览器插件扫描,重点关注
"landmark-unique"和"landmark-no-duplicate-label"规则 - 特别注意:如果
<nav></nav>没配aria-label,axe 会报"nav-has-label",但 NVDA 可能静默跳过——这不是 bug,是规范要求
最常被忽略的点:语义路标依赖的是最终渲染的 DOM 结构,不是 JSX 或模板语法。框架层的抽象、SSR/CSR 差异、服务端注入的 wrapper div,都可能悄悄破坏
<main></main>的顶层位置或<nav></nav>的可聚焦性。验证必须在真实渲染后的页面上做。 - 主导航、页脚链接等无强顺序关系 → 用
<main></main> 唯一性不是建议,是硬性约束
<main></main> 是屏幕阅读器定位“核心内容”的唯一锚点,Lighthouse 报 "Multiple main landmarks" 不是警告,是直接判为无障碍失败。一旦嵌套在 <header></header>、<section></section> 或 React 的 <layout></layout> 组件壳里,就等于把 GPS 定位点塞进了抽屉——辅助技术找不到它。
正确结构只能是:











