屏幕阅读器适配核心是语义化 dom 结构,原生标签(如 、、–)优先于 aria;表单需显式关联 label;动态内容须用 aria-live;skip-link 必须首个可聚焦且锚点有效。

屏幕阅读器适配不是加几个 aria- 属性就完事的,核心是让 DOM 结构本身可读、可导航、可理解——ARIA 只是补漏,不是替代语义。
用对语义标签比写 ARIA 更重要
浏览器和屏幕阅读器默认信任 <nav></nav>、<main></main>、<article></article>、<button></button> 这类原生标签的含义。一旦你用 <div role="button"> 代替 <code><button></button>,就得手动补全焦点、键盘事件、状态(aria-pressed)、禁用逻辑(aria-disabled)——稍有遗漏,读屏器就读错或跳过。
-
<button></button>自带可聚焦、空格/回车触发、disabled自动屏蔽朗读,别为了“样式自由”弃用它 -
<nav></nav>比role="navigation"兼容性更好,尤其在 iOS Safari + VoiceOver 下 -
<h1></h1>–<h6></h6>必须体现真实层级,不能只为字号大而乱用;跳过链接依赖<main id="main-content"></main>,不是role="main"
表单控件必须显式关联 label
没关联的 <input> 在 NVDA 或 VoiceOver 下大概率只读成“编辑框”,用户完全不知道该填什么。嵌套写法最稳:<label>邮箱<input type="email"></label>,但注意内部不能塞 <div> —— 部分读屏器遇到块级元素会中断朗读。
<ul>
<li>用 <code>for/id 关联时,确保 ID 值大小写完全一致、全局唯一
input type="button" 的 value 会被朗读,但中文空格、省略号(如 value=" " 或 value="...")会导致信息丢失,优先改用 <button></button> + 显式文本disabled,别用 pointer-events: none + CSS 灰色模拟——读屏器仍会把它当可操作项朗读动态内容更新必须带 aria-live
用 innerHTML = "" 替换整个区域后,如果新内容里没重写 aria-live,后续更新就再也不会被朗读。这不是“一次设置永久生效”的属性。
-
aria-live="polite":等用户停顿再读,适合非紧急更新(如搜索结果刷新) -
aria-live="assertive":立刻打断当前朗读,仅用于错误提示、验证失败等强干预场景 - 不要滥用
aria-labelledby跨区域关联——多一层引用,就多一重失效风险;日常表单用label或aria-label更直接
skip-link 必须是页面第一个可聚焦元素
它得是用户按 Tab 键时第一个碰到的东西,否则键盘和读屏用户还得先穿过整个导航栏。很多实现失败,是因为用了 tabindex="-1" 或藏在 JS 初始化之后才插入 DOM。
- 必须用原生可聚焦标签(
<a href="#main-content"></a>或<button></button>),不能靠tabindex="0"强行加 - 目标锚点必须存在且可见(不能
display: none或visibility: hidden),ID 大小写敏感 - 隐藏方式只能用
position: absolute; left: -9999px;,禁用clip-path、transform、opacity——部分读屏器不触发重绘
最常被忽略的是:语义结构和 DOM 顺序必须同步。比如用 JS 动态移动一个 <h2></h2> 到页面底部,却不更新其上下文关系,读屏器仍按原始 DOM 顺序朗读,逻辑就断了。











