引言
随着 Web 应用日益复杂化和动态化,开发者面临的不仅是功能实现上的挑战,还有日益严峻的无障碍问题。传统的 HTML 语义元素在处理静态内容时表现出色,但当涉及复杂的 UI 控件、动态内容更新以及非标准交互时,其表达能力就显得捉襟见肘。WAI-ARIA 正是为解决这些问题而诞生的技术规范,它通过一组额外的 HTML 属性来弥补语义的缺失,让辅助技术能够正确理解并传达页面内容。
什么是 WAI-ARIA

背景与诞生
在 HTML5 提供 nav、footer、aside 等语义标签之前,开发者通常使用带有 id 或 class 的 div 来构建页面结构。例如主导航通常写作 <div class="nav">。这种做法对于视力正常的用户来说没有问题,但对于依赖屏幕阅读器的用户而言,这些 div 无法传达任何结构信息,用户无法快速定位到主导航或侧边栏等重要区域。
早期的解决办法是在页面顶部放置隐藏的跳转链接,例如:
<a href="#hidden" class="hidden">Skip to navigation</a>
这种方法虽然在一定程度上提供了帮助,但使用场景有限,且在不同辅助技术中的表现并不一致。
另一个典型的场景涉及复杂的表单控件,如日期选择器或范围滑块。HTML5 提供了对应的 input 类型,但跨浏览器兼容性差且样式难以定制,因此开发者往往借助 JavaScript 库来实现这些功能。这些库通常使用大量嵌套的 div 构建界面,视觉上功能完整,但屏幕阅读器只能看到一堆结构混乱且毫无语义的元素。
WAI-ARIA 的核心概念
WAI-ARIA 是 W3C 制定的一套规范,定义了可以在其他元素上使用的 HTML 属性,用以补充缺失的语义信息。规范主要包含三大类特性:
角色用于定义元素的功能。许多标志性角色与 HTML5 结构元素的语义相对应,例如 role="navigation" 对应 nav 元素,role="complementary" 对应 aside 元素。此外还有描述更具体 UI 结构的角色,如 role="tablist"、role="tab"、role="search" 等。
属性为元素赋予额外的含义或语义信息。例如 aria-required="true" 表示表单字段必须填写,aria-labelledby 允许引用页面中的其他元素作为标签,这弥补了 label 元素只能关联单个输入框的局限。
状态用于表达元素当前的条件,与属性不同的是状态会随着应用的生命周期发生变化。例如 aria-disabled="true" 表示表单输入处于禁用状态,开发者可以通过 JavaScript 动态更新这一状态。
WAI-ARIA 的重要特征在于它不会对页面的视觉呈现和 DOM 结构产生任何影响。它的作用仅限于通过浏览器向无障碍 API 暴露更多信息,这些信息最终被屏幕阅读器等辅助技术所利用。开发者也可以利用这些 ARIA 属性作为 CSS 选择器进行样式定制。
何时使用 WAI-ARIA
使用 WAI-ARIA 应遵循一个核心原则:仅在必要时使用。理想情况下,开发者应当优先使用原生 HTML 元素来实现语义表达,因为原生元素自带键盘交互和无障碍支持,无需额外工作。但现实情况下存在两种必须借助 ARIA 的场景:一是对代码的控制权有限,无法修改底层 HTML 结构;二是需要实现原生 HTML 无法支持的复杂功能。
WAI-ARIA 主要解决了四个核心领域的问题:
- 路标与地标:ARIA 的角色属性可以为页面不同功能区域提供语义标识,帮助屏幕阅读器用户快速导航。
- 动态内容更新:aria-live 属性可以通知屏幕阅读器关注特定区域的内容变化。
- 键盘无障碍操作:通过 tabindex 属性的扩展值,开发者可以将原本无法聚焦的元素纳入键盘导航序列。
- 非语义控件:当开发者必须使用 div 等非语义元素构建复杂 UI 时,ARIA 可以为其补充缺失的语义信息。
路标与地标的实现
基础结构分析
以一份典型的页面结构为例,包含页眉、导航、主内容、侧边栏和页脚。使用 HTML5 语义标签编写如下:
<header>
<h1>页面标题</h1>
<nav>
<ul>
<li><a href="#">首页</a></li>
<li><a href="#">关于</a></li>
</ul>
<form>
<input type="text" placeholder="搜索" />
</form>
</nav>
</header>
<main>
<article>文章内容</article>
<aside>侧边栏信息</aside>
</main>
<footer>页脚信息</footer>
在支持 HTML5 的浏览器中,屏幕阅读器能够识别这些语义元素并告知用户。例如 VoiceOver 会报告 banner、navigation、main、complementary 和 footer 等地标。
使用 ARIA 增强地标
尽管 HTML5 语义标签已能提供基本的地标信息,但仍有优化空间。搜索表单是用户经常需要快速定位的功能,可以为表单添加 role="search" 来明确其用途:
<header>
<h1>页面标题</h1>
<nav role="navigation">
<ul>
<li><a href="#">首页</a></li>
<li><a href="#">关于</a></li>
</ul>
<form role="search">
<input type="search" name="q" placeholder="搜索关键词" aria-label="搜索整个站点内容" />
</form>
</nav>
</header>
<main>
<article role="article">文章内容</article>
<aside role="complementary">侧边栏信息</aside>
</main>
<footer>页脚信息</footer>
在这个优化版本中,search 角色使搜索表单在地标菜单中成为独立项目,便于快速访问。同时,input 元素上的 aria-label 属性为没有可见标签的输入框提供了描述性名称,屏幕阅读器会将其朗读给用户。
对于仍在使用 IE8 等老旧浏览器的用户,这些 ARIA 角色的价值更为突出。如果页面完全使用 div 构建,ARIA 角色几乎是提供必要语义的唯一手段。
动态内容更新的通知
实时区域的问题
现代 Web 应用中频繁出现动态内容更新,例如实时聊天、股票行情、购物车状态变化等。这些内容变化虽然视觉上可见,但默认情况下屏幕阅读器无法感知。用户可能完全不知道页面中的某个区域已经更新了内容。
以下是一个简单的随机名言展示示例,页面每隔十秒更新一次引用的内容:
<section>
<h1>随机名言</h1>
<blockquote>
<p></p>
</blockquote>
</section>
JavaScript 代码通过 fetch 加载数据,并使用 setInterval 定时更新内容:
const intervalID = setInterval(showQuote, 10000);
功能本身没有问题,但屏幕阅读器用户无法获知内容正在发生变化。
aria-live 的解决方案
aria-live 属性可以将元素标记为实时区域,当内容发生变化时,屏幕阅读器会朗读更新内容。该属性有三个可选值:
- off:默认值,不进行任何通知。
- polite:在用户空闲时进行通知,不会打断当前操作。
- assertive:尽快通知用户,可能会轻微打断当前操作。
- rude:立即通知,可能严重打断用户操作,应谨慎使用。
对于大多数场景,assertive 级别的通知已经足够。将实时区域标记如下:
<section aria-live="assertive">
<h1>随机名言</h1>
<blockquote>
<p></p>
</blockquote>
</section>
aria-atomic 确保完整朗读
默认情况下,屏幕阅读器只朗读实际发生变化的部分文本。如果希望每次更新时都朗读整个内容区域,可以使用 aria-atomic 属性:
<section aria-live="assertive" aria-atomic="true">
<h1>随机名言</h1>
<blockquote>
<p></p>
</blockquote>
</section>
设置为 true 后,屏幕阅读器会将整个 section 视为一个不可分割的单元,每次更新都会完整朗读标题和内容。
键盘无障碍的优化
tabindex 的扩展用法
HTML 中可聚焦的元素默认包括链接、按钮和表单控件。开发者可以通过键盘的 Tab 键在这些元素之间顺序导航。但对于使用 div 或 span 构建的自定义控件,默认是不可聚焦的,键盘用户无法访问。
WAI-ARIA 扩展了 tabindex 属性的用法,提供了两个关键值:
- tabindex="0":使元素成为可 Tab 聚焦的,它会按照 DOM 顺序出现在 Tab 键的导航序列中。这是最常用的值。
- tabindex="-1":允许元素通过 JavaScript 编程方式获得焦点,例如在点击事件中调用 focus 方法,但元素本身不会出现在 Tab 键的导航序列中。
实践示例
将一个 div 改造为可交互的按钮时,需要同时添加 tabindex 和键盘事件处理:
<div data-message="第一条消息" tabindex="0" role="button">
点击我
</div>
对应的 JavaScript 需要处理键盘事件,确保按下空格键或回车键时触发与鼠标点击相同的操作:
const button = document.querySelector('div[role="button"]');
button.addEventListener('click', handleClick);
button.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick.call(this, e);
}
});
这种做法使得自定义控件既可通过鼠标操作,也可通过键盘操作,实现了设备无关的交互。
非语义控件的语义补充
表单验证中的 ARIA
表单验证是无障碍实践中的重要环节。在错误信息的展示中,可以使用 role="alert" 将错误容器标记为告警区域:
<div class="errors" role="alert" aria-relevant="all">
<ul></ul>
</div>
role="alert" 自动将元素转换为实时区域,当错误信息出现时,屏幕阅读器会立即朗读。aria-relevant="all" 则指示屏幕阅读器在错误列表发生任何变化时都进行通知,包括添加和删除。
对于必填字段,可以使用 aria-required 属性进行标记:
<input type="text" name="name" id="name" aria-required="true" />
<input type="number" name="age" id="age" aria-required="true" placeholder="请输入 1 到 150 之间的数字" />
屏幕阅读器在聚焦这些输入框时会朗读 required 提示,让用户了解该字段必须填写。
描述非语义按钮
当开发者不得不使用 div 构建按钮时,仅添加 tabindex 无法解决语义问题。屏幕阅读器仍然无法识别这个元素是一个按钮:
<div data-message="这是第一个按钮" tabindex="0">
点击我
</div>
屏幕阅读器可能会将其朗读为 group 或其他不明确的角色。添加 role="button" 可以解决这一问题:
<div data-message="这是第一个按钮" tabindex="0" role="button">
点击我
</div>
此时屏幕阅读器会正确地将该元素识别为 button,并向用户传达其功能。即便如此,开发者仍应优先使用原生的 button 元素,因为它提供了所有内置的无障碍支持,包括键盘交互、焦点管理和语义信息。
复杂小部件的 ARIA 实现
选项卡界面的 ARIA 改造
以选项卡界面为例,原始实现使用链接列表作为选项卡触发器,内容面板则是普通 div。这种结构在功能上可以工作,但屏幕阅读器无法理解选项卡之间的关系。
ARIA 优化的版本使用了以下结构和属性:
<ul role="tablist">
<li class="active" role="tab" aria-selected="true" aria-setsize="3" aria-posinset="1" tabindex="0">
标签一
</li>
<li role="tab" aria-selected="false" aria-setsize="3" aria-posinset="2" tabindex="0">
标签二
</li>
<li role="tab" aria-selected="false" aria-setsize="3" aria-posinset="3" tabindex="0">
标签三
</li>
</ul>
<div class="panels">
<article class="active-panel" role="tabpanel" aria-hidden="false">
标签一的内容
</article>
<article role="tabpanel" aria-hidden="true">
标签二的内容
</article>
<article role="tabpanel" aria-hidden="true">
标签三的内容
</article>
</div>
role="tablist" 标记了选项卡容器的角色。role="tab" 和 role="tabpanel" 分别定义了选项卡和内容面板。aria-selected 指示当前选中的选项卡。aria-hidden 控制内容面板是否对辅助技术可见。aria-setsize 和 aria-posinset 共同告知屏幕阅读器当前元素在系列中的位置,例如 1 of 3 或 2 of 3。
这些属性使得屏幕阅读器能够正确识别选项卡结构,并告知用户当前所在的选项卡编号和总数。
状态管理的最佳实践
在选项卡切换时,需要同步更新多个 ARIA 状态。被选中的选项卡应设置 aria-selected="true",对应的内容面板应设置 aria-hidden="false"。未选中的选项卡和面板则应设置为相反的值。
JavaScript 负责在用户切换时更新这些状态属性,同时更新对应的 CSS 类来控制视觉样式。这种做法确保了视觉呈现和无障碍语义始终保持一致。
总结
WAI-ARIA 为 Web 无障碍提供了一套强大且灵活的语义补充机制。通过角色、属性和状态三大核心概念,它弥合了复杂 UI 与辅助技术之间的沟通鸿沟。
在路标与地标方面,ARIA 角色帮助屏幕阅读器用户快速定位页面中的关键功能区域。在动态内容方面,aria-live 确保用户能够感知到内容更新。在键盘操作方面,tabindex 扩展值让自定义控件进入键盘导航序列。在非语义控件方面,role 和 aria-* 属性为 div 构建的界面补充了缺失的语义。
使用 WAI-ARIA 应遵循"仅在必要时使用"的原则。优先使用原生 HTML 元素,仅在无法实现或控制权受限时才借助 ARIA。同时,建议让真实的用户参与测试,包括使用屏幕阅读器和键盘导航的用户,他们的反馈往往能发现开发者难以察觉的问题。



