A11Y:如何使用WAI-ARIA增强Web可访问性
很多Web开发者一直都认为使用WAI-ARIA是增强Web可访问性的最佳方式,但是对于了解或者说经常构建可访问性Web应用的开发同学来说,WAI-ARIA其实对于Web可访问性来说是一种“毒药”。这么说是有一定道理的,即:“滥用WAI-ARIA还不如不使用”。这句话的意思是说,如果你对WAI-ARIA不了解,随意使用的话不但不能给Web可访问性带来好处,反而会让Web可访问性更差。你可能会为这种观点感到困惑,如果真是如此,那么接下来的内容你很有必要阅读。
WAI-ARIA是什么?
WAI-ARIA的全称是 Accessible Rich Internet Applications,俗称为 ARIA。它也是W3C规范之一。其主要作用是帮助 普通HTML无法实现的方式增强Web可访问性,比如说,如果使用ARIA得当,它可以:
- 增强交互式控件的可访问性,比如下拉菜单、弹窗,滑块等
- 为页面结构定义有用的地标
- 定义动态更新的“活动区域”
- 改善键盘可访问性和交互性
- 还有更多其他的作用...
更简单地说:
ARIA允许Web开发者创建一个只有ATs技术(比如屏幕阅读器)可以看到的内容(属性),这些属性是静态的,也是动态的。
稍为了解Web可访问性(很多人也将其称为Web无障碍设计),在对Web进行开发时,有必要向使用屏幕阅读器用户详细描述Web页面的内容,以及它正在发生的事情。比如,“焦点真的在这里”或者“这真的是一个滑块”。这就好像你在离家的时候在自己的冰箱上贴了个标签,告诉自己的家人,牛奶放冰箱了,可以随时取出来喝。
也就是说,只要有一张标签存在,它就会推翻我们对每个工具是什么的信念,或者是关于这个工具的某些东西。比如说,我们在一个<a>
标签上显式设置了role="button"
(role
是ARIA中用来指定控件或元素角色的,后面会介绍它)。原本屏幕阅读器应该告诉用户,它是一个“链接”,但实际上,屏幕阅读器说谎了,它告诉用户是“按钮”。
<a href="https://www.fedev.cn">欢迎来到W3cplus</a>
<a role="button" onClick="loc">欢迎来到W3cplus<a>
从上面这个简单的示例中我们可以看到,ARIA由可以添加到HTML的标记组成,以便清楚地传达用户界面元素的 角色、状态 和 属性。这些信息帮助屏幕阅读器和其他辅助技术更好地理解Web页面上的元素,并让用户能够有效地与这些元素交互。
而且,在Web页面交互中有的时候缺少ARIA相关的属性是无法告诉依赖屏幕阅读器或其他辅助技术的用户。比如,用户点击Web页面上的一个按钮会触发页面上的某些动作,比如页面顶部会有一条消息弹出来,通知用户操作是否成功。仅使用HTML,屏幕阅读器用户不会知道这个消息已经出现,即使他们怀疑它已经出现,他们可能也不太好获取到相应的信息。使用ARIA,开发人员只需要将role="alert"
添加到将出现消息的容器中。然后,当容器的内容发生变化时,屏幕阅读器会向用户宣读这条消息。用户的焦点将保持在原来的位置,这样他们就可以继续工作。
如果您正在为web页面开发动态的、丰富的、交互式的用户界面元素,那么它们必须包含ARIA标记,否则它们很难被访问。
时至今日,ARIA的发展经历了三个版本的变更,即可 ARIA 1.0、ARIA 1.1 以及现在最新的一个版本 ARIA 1.2。
WAI-ARIA不是什么?
简单地说,WAI-ARIA的角色,状态和属性对于Web开发者来说,更多的时候是将他们视为HTML的标签属性。一般情况下,ARIA是不会影响Web页面的外观,除非你特意在CSS文件中使用一些CSS属性选择器,给元素设置设置了样式,比如像下面这样:
.button {
background-color: var(--color-button-passive);
}
.button[aria-selected="true"] {
background-color: var(--color-button-selected);
}
ARIA也不会影响鼠标或键盘用户的行为。只有使用辅助技术的用户才能感知到使用了ARIA与否之间的差异。Web开发人员可以给任意HTML标签添加任意的ARIA。这样做虽然不会影响不使用辅助技术的用户,但对于使用辅助技术的用户而言,这将可能是致命:
NO ARIA is better than bad ARIA!
其实,很多时候ARIA不应该是构建可访问Web的焦点(意思是Web开发者不言过度依赖ARIA来构建可访问性Web应用),特别是有了HTML5规范之后。因为HTML5解决了屏幕阅读器常常面临的许多语义问题。
而且其中最重要的是:
ARIA永远无法替代语义化HTML标签。
拿一个示例来解释这一点。比如现在很多依赖React,Vue等框架开发的同学,他们从不关注语义化的HTML标签,甚至是不知道怎么使用有语义化的标签。另外就是在移动端上开发,很多Web开发者不太喜欢使用原生的<a>
、<button>
标签之类的,就拿<button>
来说吧,开发者不喜欢其默认的样式,但在Web中又离不开<button>
的功能,就喜欢使用带有role="button"
的<div>
来替代<button>
,可这样做,<div>
并不具备<button>
默认的所有功能:
<button class="button">确认</button>
<div role="button" class="button">确认<div>
对于<button>
标签而言,它有一些内置的默认功能。开发者不需要做任何事情,屏幕阅读器就知道他是按钮。除些之外,对于依赖键盘操作的用户来说,<button>
也更友好,通过Tab
键可以让<button>
得到焦点,但<div>
默认是没有的:
正如上图所示,用户按键盘的Tab
键时,可以让<button>
得到焦点,并且得到焦点时,有一个:focus
状态的样式,显式告诉用户。如果要让像<div>
这种非聚焦元素有焦点,需要显式设置tabindex
:
<button class="button">确认</button>
<div role="button" class="button" tabindex="0">确认<div>
有关于**
tabindex
**更详细的介绍,可以阅读《使用tabindex
的正确姿势》一文。
在<div>
标签上显式的使用role="button"
可以让屏幕阅读器知道他是一个“按钮”,并且使用tabindex="0"
可以使Tab
让其获得焦点,但并不能修复“点击事件”。我们必须给其添加相应的事件,比如点击事件click
:
<div role="button" class="button" tabindex="0" onClick="alert(this)">确认<div>
如果再监听div.button
的keypress
事件时,用户按Tab
键将焦点移动按钮上,用户再按Enter
或Space
键盘,将会触发该键盘事件:
document.querySelector("div.button").addEventListener("keypress", (e) => {
console.log(e);
});
在调试器上将会输出相应的参数:
还可以根据e.code
来指定按钮值:
document.querySelector("div.button").addEventListener('keypress', e => {
if (e.code === 'Enter' || e.code === 'Space') {
alert('pressed!')
}
})
ARIA如何工作?
屏幕阅读器或其他辅助技术会向浏览器询问关于每个元素的信息。当ARIA出现在元素上时,浏览器接受该信息并更改它告诉屏幕阅读器的有关该元素的信息。
也就是说,ARIA通过更改和补充标准DOM无障碍树来发挥作用。
尽管 ARIA 允许我们为任何页面元素对无障碍树进行细微(乃至彻底的)修改,但那却是其唯一更改之处。ARIA 不会补充元素的任何固有行为;它不会使元素可获焦点,也不会为其提供键盘事件侦听器。那仍旧是我们开发任务的组成部分。
必须要了解的是,不需要重新定义默认语义。 无论如何使用,标准 HTML <button>
元素都不需要额外的 role="button"
ARIA 属性就能正确声明。
同样值得注意的是,某些 HTML 元素上可以使用的 ARIA 角色和属性会受到限制。 例如,不得对标准 <input type="text">
元素应用任何额外角色/属性。
为什么要使用ARIA?
有时,我们只是将ARIA用作一种增强,而不是最大限度地使用ARIA。在一些HTML的标记上添加一些ARIA会很有用。ARIA可以修改现有元素语义,也可以向不存在原生语义的元素添加语义。它还可以表达HTML中根本不存在的语义模式,例如菜单或标签面板。
ARIA允许我们创建的Web控件(组件)型元素通常无法通过普通HTML实现。
例如,ARIA可以添加只向辅助技术API公开的附加标签和说明文本:
<button aria-label="关闭">
<svg aria-hidden="true" focusable="false" role="img">
<path id="..." />
</svg>
</button>
ARIA表达的元素语义关系能够扩展标准父项、子项联系,例如控制特定区域的自定义滚动条:
<div role="scrollbar" aria-controls="main"></div>
<div id="main"></div>
简单地说,ARIA可以使用一些属性达到元素之间的具有一定的绑定关系(关联),比如在《聊聊aria-label
、aria-labelledby
和aria-describedby
》一文中介绍的aria-labelledby
和aria-describedby
属性就可以和元素的id
关联起来:
并且ARIA可以使页面的某些部分具有“实时性”,让它们在发生变化时立即通知辅助技术。
<div aria-live="true">
<span>Good: $400</span>
</div>
简单地总结一下,在Web开发的时候,虽然HTML标签中有很多具有语义的标签,但在制作一些控件时,并不能很好的通知辅助技术。这个时候我们需要使用ARIA来增强这方面的能力。但这并不意味着,ARIA在HTML中可以随时随地的使用,因为使用不当,会给辅助技术带来麻烦,会增加用户的理解成本。
何时应该使用ARIA?
ARIA应该在原生HTML标签失去语义或失败的地方使用,也就是说,当HTML(HTML5)元素没有可访问性支持时。
HTML5 Accessibility 站点列出了HTML5元素列表,并显示了对每个浏览器的可访问性支持。从这个网站我们可以获取到相应元素是否可以通过键盘访问、映射到平台可访问性API以及是否支持任何与可访问性相关的特性。比如下图所示:
可访问性的支持意味着它对依赖于辅助技术(比如屏幕阅读器)的人可用,而且开发人员不需要使用ARIA或其他额外的变通方法进行补充。
@Paciello Group公司(全球有名的易访问性咨询公司)另外整理了一份HTML5的元素和属性没有提供的角色和属性,其意思就是HTML5规范中现有的标签和属性并不能满足可访问性的需求,无法让辅助技术很好的工作。这也是我们使用ARIA的理由之一。比如说,HTML5中没有<alert>
(role="alert"
)和<tab>
(role="tab"
)这样标签。
另外一个原因就是HTML5原生标签和设计的样式相差甚远,有可能为Web开发者带来一定的额外工作量。致使Web开发者不太喜欢这些原生带有可访问性的标签,比如说,开发者喜欢使用<div>
来模拟<button>
。那么这个时候也是需要使用ARIA来增强可访问性。
还有就是,在构建定制组件时会大量使用ARIA以及动态的内容更新时需要ARIA。
注意:你只在需要的情况下使用ARIA! 理想的情况下,你只要用原生的HTML 来实现屏幕阅读器所需的语义化内容即可。有些时候这是不可能的,一来是你对于代码的整体的控制是有限的,另一方面是总会有复杂到原生HTML 无法支持的功能需要你实现。在这个场景下,ARIA 将会变成有价值的可访问优化功能。但还是要重申:
当你需要的时候再使用ARIA!
ARIA的三大核心
ARIA有三大核心,主要包括角色、属性 和 状态。
角色
ARIA中的角色主要是用来定义元素是什么或做什么。大多数HTML元素都有一个默认的角色,用于辅助技术。例如<button>
的默认角色是“按钮”,<from>
的默认角色是“表单”。ARIA可以定义HTML中不可用的角色,还可以覆盖HTML元素的默认角色。比如:
<!-- ARIA定义HTML中不可用的角色 -->
<div role="button"></div>
在HTML中<div>
是没有任何角色的,使用ARIA的role
属性设置值为button
,这个时候<div>
的角色就是“按钮”。对于有默认角色的元素,也可以使用role
来覆盖,比如:
<!-- ARIA覆盖HTML元素的默认角色 -->
<img src="path" role="button" />
<img>
默认的角色是“图片”,上面的示例将<img>
的角色变成了“按钮”角色。
值得注意的是“ARIA不会改变浏览器行为或外观”。例如,在<div>
中添加role="button"
将不会使用<div>
的外观看起来像按钮,也不会具有按钮的行为。然而,辅助技术(比如屏幕阅读器)会告诉用户它是一个“按钮”,即使它不能导航或使用键盘激活。如果在让它具有相应的交互能力或模式,那就得通过JavaScript相关的API来实现。
属性
ARIA属性定义了标准HTML中不支持的附加语义。例如,<button aria-haspopup="true">
。此属性扩展了标准按钮,使屏幕阅读器宣布该按钮如果被激活,将会触发弹出(PopUp)。
状态
ARIA状态是定义元素当前条件的属性。它们通常会根据用户交互或一些动态变量而变化。例如,<input aria-invalid="true">
。此属性将导致屏幕阅读器将此输入读取为肖前无效(这意味着需要纠正),但该状态值可以根据用户输入轻松地动态更改变false
。
状态和属性的差异之处就是:属性在应用的生命周期中不会改变,而状态可以,通常我们用编程的方法改变它,例如Javascript。
ARIA的角色、状态和属性可以在HTML的标记中定义(声明),也可以使用JavaScript对它们进行定义和动态设置和更改。ARIA状态和属性总是以aria-
开头(比如aria-hidden="true"
)。
ARIA中的角色、状态和属性涉及内容比较多,在规范中都有详细的描述,这里没办法一一和大家介绍。
ARIA自己就是一套W3C规范,可以在其规范中了解ARIA的特性以及如何使用。你可能也发现了,ARIA的这些特性可以和HTML标签元素完全的结合在一起使用(当然也可以在JavaScript中使用)。也就是说,除了阅读ARIA规范之外,还可以在HTML规范中获取ARIA相关的信息。
如何从HTML规范中窥探ARIA
作为一名Web开发人员,W3C的各种规范(比如说HTML5规范、CSS规范以及我们今天聊的ARIA规范)是非常重要的。虽然各种规范对于Web开发者很重要,但并不是每一位开发都都对规范有所了解,或者说可以从规范中获取到自己想要的信息。
我想在这里表达的是,虽然我们今天聊的是ARIA相关的特性,但并不意味着只有从ARIA规范中才能获取到与其相关的特性。其实,我们也可以从HTML规范中来窥探ARIA相关的信息。就拿<button>
元素为例吧。在HTML规范中,<button>
元素相关的规范如下图所示:
其中就有<button>
元素在可访问性方面需要考虑的一项(“Accessibility Considerations”)。如上图红色框中的那个部分。这个部分主要包括两个部分,其一是“针对作者”,另一个是针对“实现者”的。这两个链接分别会将你引导到:
从上图中不难发现:
- **“针对作者(For Authors)”**把我们引到了 ARIA in HTML 规范 中,该规范告诉作者(Web页面或应用程序的开发者)可以在HTML元素上使用ARIA的 角色(
role
) 和aria-*
属性 - **“针对实现者(For Implementers)”**把我们引到了 HTML可访问性API映射,即 HTML AAM 规范 中,该规范告诉用户代理或辅助技术(比如屏幕阅读器)的实现者,HTML元素是如何映射到平台的可访问性API的
比如说,从ARIA in HTML规范中我们可以获知。HTML的<button>
元素有一个隐式的ARIA角色,即role="button"
(这是有意义的),这就是为什么我们使用原生HTML的<button>
标签时,对于辅助技术(比如屏幕阅读器)就可以直接告诉用户这是“按钮”。另外,我们可以在<button>
元素上显式的设置role
的值为checkbox
、link
、menuitem
、menuitemcheckbox
、 menuitemradio
、option
、radio
、switch
或tab
。并且我们可以使用ARIA的aria-*
属性,不管是隐式的还是显式的,只要使用姿势正确即可。例如,只有当<button>
的role
设置为menu
或menubar
角色的后代或所属元素时,role="menuitem"
才对<button>
有效:
<div role=menu>
<button role=menuitem>重做</button>
<button role=menuitem>撤销</button>
</div>
仔细看看,描述<button>
的表格中各项条目有很多链接,一个是<button>
的链接(它可以追溯到HTML规范),另一个是<button>
对应的ARIA相关的链接,比如角色和全局的aria-*
属性,它们让我们更深入地了解HTML规范中的ARIA。
来看一个示例,假设你正在创建一个tab
(选项卡)组件,并且Tab
组件中的每个选项卡使用的是<button>
元素。我们可以通过tab
对应的链接跳转到HTML ARIA规范中的tab
描述中。从这个对应的表格行中我们可以看到tab
对应的ARIA角色、状态和属性:
从这里,可以知道,role="tab"
支持ARIA的哪些状态(比如aria-selected
和aria-expanded
)和ARIA的属性(比如aria-setsize
、aria-posinset
),以及是否需要这些属性。除了tab
是什么类型的内容,以及它是否吸任何后代限制之外。ARIA的角色、状态和属性都链接到ARIA规范中各自的定义,开发者可以在对应的链接中找到创建可访问性Tab
所需的大部分信息。
在HTML AAM规范中可以获得HTML元素角色映射的对应API。同样的,在这里有一些链接,有一些会把我们引向HTML规范中,有一些会把我们引入ARIA核心API(ARIA Core AAM)规范中:
比如role="button"
中跳转过去,我们可以看到像下图这样的描述:
从这里我们可以容易的获得role="button"
有三种不同的角色映射。如果它有aria-pressed
属性,一些平台可以映射到一个更具体的“切换按钮”角色。如果它有aria-haspopup
有一个布尔值,一些平台可以映射到一个“菜单按钮”角色。如果没有这两个属性,那么它就映射为一个“按钮”角色。
其他HTML元素也可以按照这个方式来获取相信的可访问性对应的信息。比如<a>
元素:
除了从HTML规范中对应的元素中获取ARIA相应的信息之外,还可以直接从 WAI-ARIA规范 和 ARIA in HTML规范 中获取相应信息。同样拿button
为例,我们可以在WAI-ARIA规范中直接找到role
值为button
相关的描述:
在ARIA in HTML规范中也可以找到role="button"
相关的描述(从HTML规范中role="button"
跳转过来一样):
Web开发者懂得从规范中获取自己需要的信息是最基础的一步,因为我们在开发过程中碰到问题时,规范是第一时间可以帮助我们找到解决问题的依据。
使用ARIA的五大原则
虽然说ARIA可以可以帮助我们构建具有可访问性的Web应用,但这并不是完全正确的说法。只能说,在适当的时候使用正确的ARIA角色、属性和状态,才能构建更具有可访问性的Web应用。然而,ARIA规范又是一个复杂的体系,涉及的内容也很多,如果想通过规范彻底的了解清楚ARIA中关于角色、属性和状态所有知识点,那也不是件易事。
换句话说,如果你对ARIA规范并不了解,又不想在使用ARIA出错,那可以坚持下面这几个原则,可以帮助你在使用ARIA中尽可能的少犯错。这五个规则也是ARIA规范是提供的,具体描述如下。
如果你可以使用HTML原生的元素或属性,就不要使用ARIA
HTML是Web最基础的部分,也是构建可访问性Web的最基础。当HTML原生元素具有可访问性,即HTML元素为可访问性提供了足够的语义时,就不应该使用ARIA。因为,不正确地使用ARIA,可能会给可访问性引起严重的障碍。
不到万不得已时不要使用ARIA更改HTMl标签原生语义
HTML中大多数元素都可以给辅助技术(比如屏幕阅读器)传达正确的语义或含义。不过,ARIA的角色是可以覆盖和更改标签元素的默认语义。
比如,<ul>
元素定义了一个无序列表,当屏幕阅读器遇到它时,它被识别为无序列表。屏幕阅读器会公布列表项的数量,启用列表项导航,等等。比如:
<ul>
<li>CSS</li>
<li>React</li>
<li>JavaScript</li>
</ul>
如果我们在<ul>
上显示设置role="navigation"
,那么列表语义将被覆盖并丢失。元素现在变成一个导航地标,无序列表的可访问性优势也消失了。相反<div role="navigation"><ul>...</ul></div>
同时提供了navigation
地标和无序列表(ul
)优点。事实上,做得更好的是<nav><ul>...</ul>
,在语义上他们是等价的,而且完全不依赖ARIA。
<ul>
<li>CSS</li>
<li>React</li>
<li>JavaScript</li>
</ul>
<ul role="navigation">
<li>CSS</li>
<li>React</li>
<li>JavaScript</li>
</ul>
<div role="navigation">
<ul>
<li>CSS</li>
<li>React</li>
<li>JavaScript</li>
</ul>
</div>
<nav>
<ul>
<li>CSS</li>
<li>React</li>
<li>JavaScript</li>
</ul>
</nav>
所有依赖ARIA实现的交互式控件必须提可以使用键盘操作
ARIA设计模式(ARIA Design Patterns)定义了使用ARIA构建的带有交互的Web控件(组件)。对于这些控件需要提供相应的键盘操作,即允许每个人使用键盘来操作这些控件,并确保屏幕阅读器提供的指令与页面中的实际功能保持一致。
例如,如果使用role="button"
,则元素必须能够接收焦点、用户必须能够使用Enter
(或Return
)和空格键激活与元素相关的操作。
交互式控件必须具有适当的语义,并且不能被隐藏
任何可聚焦的元素(主要使用键盘Tab
键)都必须具有适当的语义,以便将其标识为链接、按钮、表单控件等,或具有适当角色值的其他元素。因为role="presentation"
删除了语义,所以它永远不能应用于可聚焦的元素。
同样,交互元素必须是可见的。不要使用CSS或aria-hidden="true"
隐藏焦点元素。
所有交互元素都必须有一个可访问的名称
当遇到交互式控件时,必须向屏幕阅读器用户显示描述该控件的文本。按钮必须具有描述性文本(比如“确定”),文本输入框(input
)必须具有描述性标签(label
)。屏幕阅读器宣布用来标识控件的内容称为其“可访问名称”。ARIA还可以用定义可访问的名称。
错误使用ARIA的危险
ARIA如果使用不对的话,不但不能增强Web的可访问性,反而会给使用屏幕阅读器使用带来更大的困惑和不好的体感。我们在使用ARIA时,应该尽可能的避免一些事情。
忘记使用原生HTML元素
在构建Web页面时,应该尽可能的避免使用无语义的标签元素。例如,开发人员可能认为<div>
元素比<button>
元素更容易处理样式(达到设计师的需求)。可使用<div>
的缺点是,为了使<div>
的行为类似于<button>
元素,开发人员需要为事件处理程序添加额外的标记和JavaScript。比如:
<div aria-label="返回" role="button" tabindex="0"></div>
由于<div>
元素在默认情况下不能像<button>
元素那样获得焦点,因此还需要添加tabindex="0"
让<div>
可以获得焦点。
下面是一个使用<button>
的示例:
<button aria-label="返回"></button>
<button>
元素在默认情况下具有“按钮”角色,并且在默认情况下是可获得焦点的。第一个示例是可访问的,但是最好使用<button>
元素,因为它已经具有按钮默认的功能,可以节省开发时间。
给元素添加冗余角色
如果你已经使用了像<button>
这样有语义化的HTML元素,就不需要在元素上添加类似role="button"
的ARIA角色。因为<button>
元素已经具有隐式的ARIA角色,这意味着浏览器已经将role
设置为button
。
所以我们应该避免向下面这样使用:
<button role="button">确认</button>
<h1 role="header">避免冗余角色</h1>
不建议添加额外的ARIA角色,添加了也不会增加任何好处。
也就是说,对于有语义的HTML标签元素,我们就应该直接使用,就像下面这样:
<button>确认</button>
<h1>避免冗余角色</h1>
覆盖了元素的原生语义
在ARIA规则中提到过,我们不应该使用ARIA来覆盖HTML标签元素原生语义。
<img role="button" src="path" alt="返回" >
即使要使用图像当作按钮,我们应该像下面这样做:
<button>
<img src="path" alt="返回" />
</button>
或者:
<div role="button" tabindex="0">
<img src="path" alt="返回" />
</div>
使用aria-hidden
不当
在《Web隐藏术》一文中我们可以得知对屏幕阅读器隐藏元素或Web隐藏元素有不同的方式,但是在使用aria-hidden
时要小心一点。在元素上使用aria-hidden="true"
时,屏幕阅读器无法对该元素获得焦点,也不会对用户朗读。比如说,在<button>
上显式设置了aria-hidden="true"
将从可访问树中删除它。
<button aria-hidden="true">确认</button>
屏幕阅读器也无法获得焦点。即使显式设置了tabindex
也是一样无法让按钮获得焦点。
<button aria-hidden="true" tabindex="-1">确认</button>
因此,只有不希望屏幕阅读器识别(获得焦点)时才使用aria-hidden="true"
。
使用aria-live
不当
当页面内容的某些部分动态更新时,最好让屏幕阅读器用户知道这些更改。通过使用JavaScript可以将文本插入使用aria-live
属性的容器中,该容器将向屏幕阅读器用户朗读变更的文本。
不过,你打算使用aria-live
时,不需将其放在<body>
元素中:
<body aria-live="assertive"></body>
因为这样做,在<body>
中发生的每一个变化都将被屏蔽阅读器读取并中断用户的操作。
如果真有这样的需求,应该只在文本有变更的容器中使用aria-live
,比如:
<div aria-live="polite"><!-- 更新的公告 --></div>
避免只在aria-label
添加描述
在《聊聊aria-label
、aria-labelledby
和aria-describedby
》一文中介绍aria-label
时,aria-label
的内容会覆盖元素自身的文本内容。因此我们在使用aria-label
时也需谨慎。
在开发表单时,需要给视力正常的用户和使用屏幕阅读器用户提供一些表单填写规则。下面这样使用是不对的:
<form>
<label for="new-password">新密码:</label>
<input type="password" id="new-password"
aria-label="您的新密码必须至少为10个字符,其中至少有一个小写、一个大写、一个数字和一个特殊字符。" />
</form>
仅在aria-label
中设置描述内容对于视力正常用户是看不到的,但存在于可访问树中:
我们应该使用aria-describedby
来替代aria-label
:
<form>
<label for="new-password">新密码:</label>
<input type="password" id="new-password" aria-describedby="password-instructions" />
<p id="password-instructions" aria-hidden="true">您的新密码必须至少为10个字符,其中至少有一个小写、一个大写、一个数字和一个特殊字符。</p>
</form>
视力正常用户看到的效果如下:
屏幕阅读器的用户效果如下:
忘了给父容器设置角色
务必注意,在使用ARIA角色时,父角色必须包含某些角色。比如在构建tab
组件时,需要选项卡的父容器显式设置role="tablist"
,因为它是“选项卡(role="tab"
)”角色所需的上下文。缺少必须的父角色元素将不能准确地传达与辅助技术的关系。比如下面这样使用时,就缺少了父角色:
<ul>
<li role="tab">最新博文</li>
<li role="tab">最新评论</li>
</ul>
这样使用了,Lighthouse检测时会报错:
需要在role="tab"
的父容器<ul>
上显式设置role="tablist"
:
<ul role="tablist">
<li role="tab">最新博文</li>
<li role="tab">最新评论</li>
</ul>
忘记给子元素设置角色
同样重要的是,确保为任何已有的父角色设置了子角色。
比如,角色tab
属性tablsit
的子角色,如果显式设置了role="tablist"
的子元素没有显式设置role="tab"
同样是不对的:
<ul role="tablist">
<li>最新博文</li>
<li>最新评论</li>
</ul>
它的正确做法是:
<ul role="tablist">
<li role="tab">最新博文</li>
<li role="tab">最新评论</li>
</ul>
上面这些个示例,再次向我们证明了:
虽然使用ARIA角色,状态和属性可以构建更易于访问的Web应用,但如果使用不当,很容易使情况变得更糟糕!
每个Web开发人员都应该知道的ARIA属性
当一个对可访问性不熟悉的开发人员看到ARIA规范时,可能会感到害怕,甚至不知道从何入入手,其实没有这么吓人。ARIA规范中的大部分关于ARIA的角色、状态和属性是专门用来制作Web控件(或组件)的,刚接触可访问性开发的开发人员没有必要去深入的理解。即使你真的需要构建一些具有可访问性的组件,比如滑块、模态框、手风琴等,ARIA规范中为些组件提供了最相应的文档:
但是,每个开发人员都应该能够在日常开发中使用以下ARIA角色,状态和属性。需要明白它们可以做什么,什么时候使用它们,什么时候不使用它们。
标签
在尝试满足WCAG时,标签是映射到传递给屏幕阅读器的可访问名称的属性。在HTML中,通常是这么做的:
<a></a>
标签内的文本提供其可访问名称<button></button>
标签内的文本提供其可访问名称<label for="IdName"></label>
提供表单字段的可访问名称,比如<input id="">
或<select id="">
等
下面的ARIA标签属性将会覆盖这些HTML可访问名称,因此只有在不能使用上面的通用HTML标签时才使用它们。如果你想提供补充信息,请使用aria-describedby
,它将与可访问名称分开呈现给屏蔽阅读器,并且不会覆盖它。
aria-label
定义标记当前元素的字符串值。aria-label
和aria-labelledby
目的相同。它为用户提供可识别的对象名称。label
最常见的可访问性API映射是acceessible name
属性。如果标签文字在屏幕上可见,开发者应该使用aria-labelledby
,而不应该使用aria-label
。
aria-labelledby
aria-labelldby
和aria-label
目的相同。它为用户提供可识别的对象名称。映射到可访问的名称属性。
如果界面上不可能有一个可见的标签在屏幕上,应该使用aria-label
而不应该使用aria-labelledby
。根据文本替代计算的要求,在计算可访问名称属性时,用户代理将优先于aria-labelledby
而不是aria-label
。
aria-labelledby
和aria-describedby
类似,都是引用其他元素来计算文本替代,但是标签应该简洁,其中的描述旨在提供更详细的信息。
描述
当需要向表单字段添加一些补充信息时,请使用aria-describedby
。不要使用aria-labelledby
或aria-label
,因为它将覆盖HTML的label
。
aria-describedby
属性在操作系统可访问API填充可访问描述,并用于提供附加或补充信息。目前,我建议将错误消息与其对应的表单字段关联起来,直到屏幕阅读器支持aria-errormeessage
为止。
aria-describedby
aria-describedby
和aria-labelledby
类似,都引用其他元素来计算文本替代,但是标签应该简洁,其中描述旨在提供更详细信息。
aria-describedby
引用的一个或多个元素组成了整个描述。如有必要,包括对多个元素的ID
引用,或者用ID
引用的元素包含一组元素(比如p
)。
aria-details
aria-details
属性引用一个元素,该元素提供比aria-describedby
通常提供的信息更详细。它使辅助技术能够使用户意识到扩展描述的可用性,并导航到它。开发者应该确保aria-details
引用的元素对所有用户都是可见的。
与aria-describedby
引用的元素不同,aria-details
引用的元素没有使用在可访问名称和描述规范中定义的可访问名称计算或可访问描述计算中。因此,aria-details
引用的元素的内容在提供给辅助技术用户时不会得化为字符串。这使得aria-details
在将信息转换为字符串时可能导致信息丢失或使扩展描述更难理解时特别有用。
在一些用户代理中,可访问性API不支持描述信息的多个引用关系。在这种情况下,如果对某一元素同时提供aria-describedby
和aria-details
,那么aria-details
权重更高。
aria-details
的一个常见用途是在数字出版本中,需要在需要结构标记或嵌入其他技术以提供说明性内容的书中进行扩展描述。
<img src="pythagorean.jpg" alt="Pythagorean Theorem" aria-details="det">
<details id="det">
<summary>Example</summary>
<p>
The Pythagorean Theorem is a relationship in Euclidean Geometry between the three sides of
a right triangle, where the square of the hypotenuse is the sum of the squares of the two
opposing sides.
</p>
<p>
The following drawing illustrates an application of the Pythagorean Theorem when used to
construct a skateboard ramp.
</p>
<object data="skatebd-ramp.svg" type="image/svg+xml"></object>
<p>
In this example you will notice a skateboard with a base and vertical board whose width
is the width of the ramp. To compute how long the ramp must be, simply calculate the
base length, square it, sum it with the square of the height of the ramp, and take the
square root of the sum.
</p>
</details>
标题
在HTML中,通常使用<h1> ~ <h6>
标签来设置标题,但在视觉上看上去是标题,使用的却是非<h1> ~ <h6>
标签,比如<div>
、<span>
等。仅管视觉上是标题,但屏幕阅读器并不会告诉用户是标题。
使用ARIA中的role="heading"
和aria-level
可以告诉用户:
<div role="heading" aria-level="1">我是标题</div>
在屏幕阅读器呈现给用户的效果和下面使用<h1>
等同:
<h1>我是标题</h1>
heading
标题是页面的一部分。heading
是ARIA的role
之一,用来作为章节的标题(role="heading"
元素将使用作为标题的章节的aria-labelledby
属性来引用)。如果标题被组织成逻辑大纲,aria-level
属性用于指示嵌套级别。
<div role="heading" aria-level="1"></div> › <h1></h1>
<div role="heading" aria-level="2"></div> › <h2></h2>
<div role="heading" aria-level="3"></div> › <h3></h3>
<div role="heading" aria-level="4"></div> › <h4></h4>
<div role="heading" aria-level="5"></div> › <h5></h5>
<div role="heading" aria-level="6"></div> › <h6></h6>
aria-level
aria-level
主要用来定义结构中元素的层次级别。它可以应用于树内的树项、文档内的标题、嵌套的网格、嵌套的表格以及其他可能出现在容器内或参与所有权层次结构的结构项。aria-level
的值是一个大于或等于1
的整数。
一般情况,aria-level
和role
为grid
、heading
、listitem
、row
和tablist
组合在一起使用,比如:
<div role="heading" aria-level="1"></div>
按钮和输入框
表单将是使用ARIA的核心场所之一。除了下面的这些属性,上面的标签部分也发挥了作用。使用aria-describedby
将错误信息与其相应的输入框(<input>
)关联起来。它与可访问名称(label
)分开呈现给屏幕阅读器,不像aria-label
或aria-labelledby
那样覆盖它。
aria-expanded
指示是否展开或折叠由该元素拥有或控制的分组元素。
aria-expanded
的属性应用于可聚焦的交互式元素,该元素切换另一个元素中内容的可见性。例如,它应用于父容器treeitem
,以指示是否显示树的子分支。同样,它也可以应用于控制页面内容部分可见性的按钮。
如果可以展开或折叠的分组容器不属于具有aria-expanded
属性的元素,则开发者应该通过使用aria-controls
属性从具有aria-expanded
元素引用容器来标识控制关系。
<!-- HTML -->
<a aria-expanded="true" id="TLink1" href="#" onclick="toggle('navbar', 'TLink1')">Toggle Navigation Bar 1</a>
<ul style="" id="navbar">
<li><a href="http://example.com/target1.html">Link 1</a></li>
<li><a href="http://example.com/target2.html">Link 2</a></li>
<li><a href="http://example.com/target3.html">Link 3</a></li>
<li><a href="http://example.com/target4.html">Link 4</a></li>
</ul>
<p>Here is some content below the collapsible section.</p>
// JavaScript
function toggle(targetElementId, currentElementId) {
var n = document.getElementById(targetElementId);
if (n.style.display != 'none') {
n.style.display = 'none';
document.getElementById(currentElementId).setAttribute('aria-expanded', 'false');
} else {
n.style.display = '';
document.getElementById(currentElementId).setAttribute('aria-expanded', 'true');
}
}
aria-expanded
可以用于role
为application
、button
、checkbox
、combobox
、gridcell
、link
、listbox
、menuitem
、row
、rowheader
、tab
和treeitem
元素上。也可以继承于role
为columnnheader
、menuitemcheckbox
、menuitemradio
和switch
。
aria-expanded
的值主要有true
、false
和undefined
:
false
:此元素拥有或控制的分组元素将被折叠true
:此元素拥有或控制的分组元素被展开undefined
:默认值,元素不拥有或控制可扩展的分组元素
aria-readonly
指示元素不可编辑,但在其他方面是可操作的。
这意味着用户可以读取但不能设置控件的值。仅读元素与用户相关,应用程序作者不应该将导航限制到元素或其可聚焦的后代。还支持其他操作,比如复制元素的值。这与禁用元素相反,应用程序可能不允许用户导航到其后代。
我们在表单的使用时,如果某个表单控件,比如input
输入框不允许用户编辑时,会在该元素上显式设置readonly
属性:
<input type="text" name="country" value="Norway" readonly />
那我们来看aria-readonly
的示例。比如页面包含一个由文本框和用户可以从中选择的值的下拉列表组成的组合框。选中的列表项成为文本框的值。当用户通过箭头在列表中导航时,aria-activedescendant
后代属性将进行调整,以反映已导航到的当前子元素的id
属性:
<input type="text" aria-activedescendant="cb1-opt6" aria-readonly="true"
aria-owns="cb1-list" aria-autocomplete="list" role="combobox" id="cb1-edit">
<ul aria-expanded="true" role="listbox" id="cb1-list">
<li role="option" id="cb1-opt1">Alabama</li>
<li role="option" id="cb1-opt2">Alaska</li>
<li role="option" id="cb1-opt3">American Samoa</li>
<li role="option" id="cb1-opt4">Arizona</li>
<li role="option" id="cb1-opt5">Arkansas</li>
<li role="option" id="cb1-opt6">California</li>
<li role="option" id="cb1-opt7">Colorado</li>
</ul>
aria-readonly
主要和role
为checkbox
、combobox
、grid
、gridcell
、listbox
、radiogroup
、slider
、spinbutton
和textbox
元素一起使用。它的值主要有true
和false
。
aria-required
指示在提交表单之前需要用户输入元素。
例如,如果用户需要填写一个地址字段,那么需要将该字段的aria-required
属性值设置为true
。使用aria-required
属性允许作者明确地向辅助技术传达元素是必需的。
除非有完全等价的原生属性可用,否则宿主语言应该允许作者在需要用户输入或选择的宿主语言表单元素上使用aria-required
属性。
<input type="text" name="name" required aria-required="true" />
aria-required
属性可以和role
的值为checkbox
、combobox
、gridcell
、listbox
、radiogroup
、spinbutton
、textbox
和tree
的元素结合在一起使用,它的值主要有true
和false
。
aria-controls
标识其内容或存在由当前元素控制的元素(或多个元素)。例如:
- 目录树视图可以控制相邻文档窗格的内容
- 一组复合框可以控制在表格或图表中实时跟踪哪些商品的价格
- 选项卡控制与其关联的选项卡面板的显示
aria-controls
属性是一个“关系属性”,表示交互元素或元素集对页面中的哪些元素具有控制权和影响。它通常用于描述按钮和该按钮显示的可扩展区域之间的关系。
<button aria-expanded="false" aria-controls="expandable">open / close</button>
<div id="expandable" hidden>content of the expandable region</div>
aria-disabled
指示该元素是可感知的,但禁用的,因此它不可编辑或可操作。
例如,在一个radio
组中,不相关的选项可能被禁用。被禁用的元素可能不会从选项卡顺序接收焦点。对于某些被禁用的元素,应用程序可能选择不支持地后代的导航。除了设置aria-disabled
属性外,作者还可以更改视觉外观(灰色等),以表明该项目已被禁用。
<input type="radio" disabled aria-disabled="false">
禁用状态应用于当前元素和应用aria-disabled
属性的元素的所有可聚焦后代元素。
aria-disabled
可用于role
为application
、button
、composite
、gridcell
、group
、input
、link
、meuitem
、scrollbar
、separator
、tab
的元素上,它的值主要有true
和false
。
aria-invalid
指示输入的值不符合应用程序所期望的格式。
如果计算值无效或超出范围,应用程序作者应该将该属性设置为true
。用户代理应该通知用户数据是错误的。如果已知,应用程序作者应该提供更正建议。
当用户试图提交包含aria-required
为true
的数据时,作者可能会使用aria-invalid
属性来表示出现了错误。但是,如果用户没有尝试提交表单,那么作者不应该仅仅因为用户还没有输入数据就在所需的元素止设置aria-invalid
属性。
aria-invalid
属性是枚举类型。用户代理必须将允许的值列表中未识别的值视为true
。如果属性不存在,或者它的值为false
,或者它的值为空字符串,则应用缺省值为false
。
<!-- HTML -->
<input name="name" id="name" aria-required="true" aria-invalid="false" onblur="checkValidity('name', ' ', 'Invalid name entered (requires both first and last name)');"/>
<input name="email" id="email" aria-required="true" aria-invalid="false" onblur="checkValidity('email', '@', 'Invalid e-mail address');" />
// JavaScript
function checkValidity(aID, aSearchTerm, aMsg){
var elem = document.getElementById(aID);
var invalid = (elem.value.indexOf(aSearchTerm) < 0);
if (invalid) {
elem.setAttribute("aria-invalid", "true");
updateAlert(aMsg);
} else {
elem.setAttribute("aria-invalid", "false");
updateAlert();
}
}
function updateAlert(msg) {
var oldAlert = document.getElementById("alert");
if (oldAlert) {
document.body.removeChild(oldAlert);
}
if (msg) {
var newAlert = document.createElement("div");
newAlert.setAttribute("role", "alert");
newAlert.setAttribute("id", "alert");
var content = document.createTextNode(msg);
newAlert.appendChild(content);
document.body.appendChild(newAlert);
}
}
aria-haspopup
指示可由元素触发的交互式弹出元素(如菜单或对话框)的可用性和类型。
弹出元素通常显示为位于其他内容之上的内容块。作者必须确保作为弹出内容容器的元素的角色是menu
、listbox
、tree
、grid
、dialog
,并且aria-haspopup
的值与弹出容器的角色匹配。
为了使弹出窗口元素可以通过键盘访问,作者应该确保触发弹出窗口的元素是可聚焦的,有一个键盘机制来打开弹出窗口,并且弹出窗口元素管理它的所有后代的焦点。
aria-haspopup
属性是一个枚举类型。用户代理必须处理允许值列表中未包含aria-haspopup
的任何值,包括一个空字符串,就像提供了false
值一样。为了提供对 ARIA 1.0内容的向后兼容性,用户代理必须将aria-haspopup
值true
等同于menu
的值。
<div class="btn-group">
<button type="button" class="btn btn-danger dropdown-toggle" aria-haspopup="true" aria-expanded="false">Action </button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Separated link</a>
</div>
</div>
动态更新和相应角色
每当页面上有可见的状态消息时,也应该在其周围有动态区域,以便向用户宣布。role="status"
和role="alert"
提供了一种简单的方法,可以在不使用aria-live
属性的情况下将元素转换为动态区域。status
角色隐式的具有的aria-live="polite"
,而alert
角色隐式的具有aria-live=assertive
。这两个角色都读取整个节点,即使只有一小部分更改。如果你想在aria-live
中使用它,你通常需要添加aria-atomic
。所以我们开始喜欢status
和alert
的角色替代aria-live
。
地标角色(Landmark)
大多数Web开发人员使用一些地标,即使他们也不知道。当<header>
、<main>
、<footer>
、<nav>
和<aside>
被正确使用时,它们对屏幕阅读器用户非常有用。尽可能使用这些HTML元素。在ARIA中,有一些role
角色可以和这些HTML5标签对应。这些角色被称为是ARIA地标角色。
小结
在《WAI-ARIA初探》中和大家简单的介绍了ARIA。今天我们在这篇文章中又花了很长的篇幅来和大家探讨ARIA。虽然某些情况或场景中,使用ARIA的角色、状态和属性可以帮助我们构建更具可访问性的Web应用,但我们在使用ARIA时一定要按其五大原则来做事。并且其中最关键一点:不到万不得已,不要轰易使用ARIA,应该更多的考虑HTML原生元素的使用。因为ARIA用的不恰当,它不是Web可访问性的解药,反而是毒药。