根本原因是html语义缺失:用替代、嵌套错误或缺失、/结构松散,导致可访问性树断裂,屏幕阅读器无法正确识别导航、主体和表单结构。

为什么屏幕阅读器“读不到”你的导航栏
根本原因往往不是 JS 或 CSS 问题,而是用了 <div class="nav"> 替代 <code><nav></nav>,或者把 <main></main> 套在 <table> 里——HTML 结构无效,浏览器可能直接丢弃整个节点。
<p>屏幕阅读器依赖 DOM 的语义结构生成“可访问性树”,而不是视觉布局。当它遇到 <code><nav></nav>,就知道这是导航区域,支持快捷键(如 NVDA 的 Insert+Tab)一键跳入;而 <div role="navigation"> 虽能模拟角色,但缺失隐式行为:比如默认的键盘焦点管理、<code>aria-expanded 的自动同步逻辑,且容易遗漏绑定或嵌套错误。
-
<nav></nav>必须直接包裹可聚焦元素(如<a></a>、带tabindex的<button></button>),不能只放纯文本 - 多个导航区必须用
aria-label区分,例如<nav aria-label="主导航"></nav>和<nav aria-label="页脚导航"></nav> -
<nav></nav>不能作为<table>、<code><ul></ul>等有严格子元素限制标签的子元素,否则校验失败<main></main>缺失或重复时,视障用户怎么找“正文”<main></main>是页面中唯一代表核心内容的容器,它的存在与否、是否唯一,直接决定屏幕阅读器能否准确定位“用户真正想看的部分”。一旦漏写、重复或嵌套错误,可访问性树就会断裂,用户无法通过“跳转到主要内容”快捷键抵达正文。常见误用:
<section></section>里塞一个<h1></h1>,或者<main></main>外包一层<div> 再加 <code>role="main"——前者破坏标题层级连续性,后者绕过原生语义,部分旧版 VoiceOver 可能忽略该 role。-
<main></main>全局只能出现一次,且不应被<article></article>、<aside></aside>、<footer></footer>等包围 -
<main></main>内可嵌套<header></header>和<footer></footer>,但<section></section>不应替代<main></main>的主体地位 - 如果
<main></main>里只有一个<section></section>,不如直接用<main><h1>...</h1></main>,减少冗余包装
<label></label>和<fieldset></fieldset>绑定失效的兼容性陷阱表单可访问性崩塌的高频点:输入框没关联说明文字、分组标题不可达。问题常出在结构松散——
<label></label>没用for/id显式绑定,或<fieldset></fieldset>被<div> 包裹后设 <code>role="group"。辅助技术对
<label></label>和<fieldset></fieldset>的识别高度依赖原生嵌套关系。用<div> 替代 <code><fieldset></fieldset>,哪怕补上aria-labelledby,旧版 JAWS 仍可能跳过分组标题;仅靠视觉位置或aria-label绑定<input>,在 Safari + VoiceOver 下朗读不稳定。-
<label></label>必须显式绑定:for="xxx"+id="xxx",或直接包裹<input> -
<fieldset></fieldset>必须是<input>的直接父容器,不能被<div> 中间隔开 <li> <code><legend></legend>必须作为<fieldset></fieldset>的第一个子元素,不可用<h2></h2>替代
语义化不是“锦上添花”,而是可访问性树的骨架
所有 ARIA 属性都建立在语义化 HTML 的基础上。用错标签,再精细的
aria-expanded或aria-live都救不回来——因为可访问性树的根节点已经歪了。最常被忽略的一点:语义化标签本身不保证无障碍,但它决定了屏幕阅读器“信什么”。当你写
<button></button>而不是<div onclick>,你不仅省了 <code>tabindex、keydown监听和aria-pressed管理,更重要的是,你让辅助技术默认信任这个元素“就是个按钮”。这种信任,是手动补 ARIA 永远无法 100% 模拟的。 -











