屏幕阅读器解析dom的语义角色、名称和状态,语义化标签直接提供这些信息;缺少导致正文定位困难;自带地标功能,需手动补全aria;原生支持键盘交互,需手动实现全部逻辑;须有标题且可独立存在,滥用破坏大纲结构。

屏幕阅读器不是“读HTML”,而是解析DOM的语义角色(role)、名称(name)和状态(state)。语义化标签直接提供这些信息,跳过手动补全ARIA的繁琐路径;不写语义标签,等于把所有结构理解成本甩给开发者自己——而且90%的补救都漏项。
为什么 <nav></nav> 能被NVDA/VoiceOver识别为可跳转地标,而 <div class="nav"> 完全静默
<p>屏幕阅读器不解析CSS类名,也不猜测你“想表达什么”。<code><nav></nav> 自带 role="navigation",且被主流读屏器固化为地标(landmark),支持N键跳入;<div class="nav"> 的 computed role 是 <code>generic,读屏器只当它是普通容器,连“导航”两个字都不会提。
常见错误现象:
- 多个
<nav></nav>未加aria-label,全部朗读为“导航”,用户无法区分主导航、页脚导航或面包屑 - 用
<div role="navigation"> 替代 <code><nav></nav>,但漏掉tabindex="0",键盘用户根本无法聚焦 - 在React组件中用
<div id="app"> 包全局,却没在子组件里放 <code><nav></nav>,导致整个导航区块不可跳转<main></main>缺失或重复时,屏幕阅读器如何“瞎找正文”几乎所有屏幕阅读器把
<main></main>当作“页面主体内容”的唯一可信锚点。没它,系统退而求其次:先找role="main",再找不到就从第一个<h1></h1>开始算正文——但页眉里可能有<h1></h1>,结果用户下拉7–12次才摸到真正要读的内容。硬约束必须遵守:
- 全DOM树中只能有一个
<main></main> - 不能嵌套在
<header></header>、<footer></footer>、<nav></nav>或<aside></aside>内部 - 不能包裹广告位、侧边栏、弹窗等非核心内容;否则M键跳入后,用户第一眼听到的是“双11大促Banner”,而不是商品列表
用
<button></button>还是role="button"?键盘交互差距在哪二者在读屏器里可能都读成“按钮”,但行为天差地别:
<button></button>自动支持空格/回车触发、disabled状态禁用朗读与交互、默认焦点样式;<div role="button"> 必须手动补全全部逻辑,漏一项,键盘用户就会卡住。 <p>真实踩坑点:</p> <ul> <li> <code><div>提交</div>(Vue场景):测试只点鼠标,上线后视障用户完全无法提交 - 全DOM树中只能有一个
<div role="button" tabindex="0" onclick="...">:没监听 <code>onkeydown响应空格/回车,键盘用户按了没反应- 加了
aria-disabled="true"却没同步控制JS逻辑,界面禁用但事件仍可触发 - 这块内容是否自带
<h2></h2>–<h6></h6>标题?没有就别套<section></section> - 它是否能单独被RSS抓取、邮件推送或聚合页引用?能→用
<article></article>;不能→才考虑<section></section> - 它是否属于已有专用语义标签范畴?比如页脚用
<footer></footer>、导航用<nav></nav>,就别再包一层<section></section>
<section></section> 和 <article></article> 不是 <div> 的升级版,滥用反而污染文档大纲
<p><code><section></section> 是为「有标题的独立逻辑分段」准备的,不是视觉容器。滥用会让屏幕阅读器生成冗余导航节点,用户靠标题树(T键)跳转时,满屏都是“无标题的section”,根本找不到重点。
判断依据很简单:
最常被忽略的其实是标题层级断裂:页面从 <h2></h2> 开始、<main></main> 外堆了三个 <h1></h1>、小节用了 <h3></h3> 但主内容还没出现 <h2></h2>——这会让屏幕阅读器生成的文档大纲完全错乱,比不用语义标签更伤可访问性。











