原生语义标签更可靠,因<button>天然支持焦点管理、空格/回车触发、禁用跳过及状态同步;而<div role="button">需手动补全tabindex、键盘事件、aria-disabled等,漏一项即导致读屏失效。
原生语义标签能解决 80% 的屏幕阅读器问题,aria- 属性只是补漏手段,不是替代方案。
用 <div role="button"> 模拟按钮,必须手动处理焦点、键盘事件(空格/回车触发)、禁用状态(aria-disabled)、按下态(aria-pressed)——漏一项,读屏就失效。而 <button> 天然支持全部。
disabled 属性,屏幕阅读器自动跳过;用 pointer-events: none + CSS 灰色,则仍被朗读为“可点击”,但实际点不动<button><svg></svg></button>)必须加 aria-label,例如 aria-label="删除"
input type="button" 的 value 会被朗读,但中文空格(value=" ")或空值(value="")会导致信息丢失,优先改用 <button> + 显式文本或 aria-label
没正确关联 <label> 是最常见原因。浏览器不会自动推断哪个 label 对应哪个 input,必须显式绑定。
<label>邮箱<input type="email"></label>,但内部不能有 <div> 等块级元素,否则部分读屏软件会中断朗读for/id 关联也行:<label for="email">邮箱</label><input id="email" type="email">,注意 ID 大小写必须完全一致、全局唯一aria-labelledby 跨区域关联仅限复杂场景(如一个标题描述多个字段),日常滥用反而增加失效风险单纯替换 innerHTML 或切换 display: none,读屏基本无感。必须用 aria-live 主动通知。
立即学习“前端免费学习笔记(深入)”;
<div aria-live="polite">...</div> 包裹,内容变化时会等用户停顿再朗读aria-live="assertive",但会打断当前朗读,慎用innerHTML = "" 替换整个区域后,新内容里必须重写 aria-live 属性,否则后续更新不再触发朗读aria-live 加在带 CSS 过渡(如 opacity 动画)的元素上,过渡期间 DOM 变化可能被忽略不加 scope 的 <th>,读屏大概率读成一串割裂词,而非结构化数据。每个 <th> 必须明确作用域。
<thead> 里的 <th>(如“姓名”“年龄”)→ 必须设 scope="col"
<tbody> 每行首个 <th>(如“用户A”)→ 必须设 scope="row"
rowspan/colspan)或复杂结构,scope 不够用时,改用 headers + id 显式绑定:<th id="sales">销售额</th> 和 <td headers="sales">120万</td>
<td> 上写 scope 是无效的,W3C 明确禁止,主流浏览器静默忽略最难的不是查文档写对某一行 scope,而是在每次插入 <th> 时条件反射地问一句:“它管的是列,还是行?”——这个习惯一旦建立,比任何检测工具都管用。