语义化标签是无障碍基础,必须优先使用<header><nav><main><footer>等原生标签而非div加ARIA;<main>全局唯一且不可嵌套,表单需显式label包裹,表格th必须设scope,caption须紧贴table开头并具体描述内容。

直接用语义化标签,比堆div加ARIA靠谱得多——这是最省力、最稳定、也最容易被屏幕阅读器正确识别的路径。
为什么必须唯一且不能嵌套
很多项目把整个页面内容塞进一个div#app里,再靠JS挂载,结果<main></main>要么缺失,要么被包在<header></header>或<footer></footer>里。屏幕阅读器用户按Ctrl+Alt+Home(NVDA)或R(JAWS)跳转主内容时直接失效。
-
<main></main>只能出现一次,且不能是<article></article>、<aside></aside>、<nav></nav>、<header></header>、<footer></footer>的子元素 - 如果用了框架(如React/Vue),确保
<main></main>在真实DOM中处于顶层,不被包裹在无意义的div里 - 检查方式:DevTools里右键「Reveal in Elements Panel」,确认
<main></main>直接挂在下
表单控件必须有
动态生成表单时,JS拼HTML容易漏掉label,或for值与实际id不一致。后果是屏幕阅读器读不出“邮箱输入框”,语音指令完全失效。
- 优先用显式包裹:
<label>邮箱<input type="email"></label>,避免for/id错配风险 - 禁用空
<label for="xxx"></label>——这会让阅读器静默跳过,用户根本不知道这是什么控件 - 多选/单选组必须用
<fieldset>+<legend>,仅靠视觉分组不会被读出组标题
表格里scope不是可选,而是强制声明
没写scope,<th>就只是加粗文本。浏览器和读屏器不会凭位置自动推断“第一行管列、第一列管行”。用户听到的是一串孤立数字,而不是“张三,年龄,32”这种语义关系。
立即学习“前端免费学习笔记(深入)”;
-
<th scope="col">只能出现在<thead>中,用于横向列标题(如“姓名”“城市”) -
<th scope="row">只能出现在<tbody>每行第一个<th>,不能放在<td>里 - 没有表头的列(如操作列),别用
<th>,直接用<td> - 真正难的不是查文档写对
scope,而是在每次敲下<th>时,下意识问一句:“它管的是行,还是列?”
caption必须紧贴table开头,且不能空泛
<caption>不是视觉装饰,是屏幕阅读器进入表格时最先播报的上下文说明。位置错(比如塞进<thead>里)、内容空泛(如写成<caption>数据表</caption>),都会让用户失去对表格意图的基本判断。
- 必须是
<table>的第一个子元素,不能被<thead>或<tbody>包裹 - 内容要具体,例如
<caption>2026年Q1前端班学员考核成绩</caption>,而非“数据表”或“表格1” - 禁用已废弃的
summary属性——现代检测工具(如 axe)直接报错,VoiceOver/NVDA 完全忽略
最难的不是记住所有规则,而是在写<div class="nav">前,本能地停顿半秒,换成<nav>;在敲<th>时,条件反射般补上scope;在加input前,先想好怎么把它包进label里——这些动作本身,就是无障碍落地最真实的刻度。



















