理解WAI-ARIA:综合指南
网络无障碍倡议--可访问的富互联网应用(WAI-ARIA)是一个技术规范,为如何提高网络应用的可访问性提供了方向。网络内容可及性指南(WCAG)更多关注的是静态的网络内容,而WAI-ARIA关注的是如何使交互更加可及。
网络上的互动因其不可访问性而臭名昭著,而且往往是最关键功能的一部分,例如:
- 提交工作申请
- 从网上商店购买或
- 预约医疗服务
我目前是Fable公司的无障碍创新主管,该公司将组织与残疾人联系起来进行用户研究和无障碍测试,并为数字团队提供定制培训,以获得构建包容性产品的技能。
作为无障碍网络开发的讲师,我花了很多时间检查网站和网络应用的源代码,ARIA是我看到的开发者滥用最多的东西之一。
HTML
当你使用HTML元素,如input
、select
、button
,有两件事你会得到无障碍性:关于该元素的信息被传递到DOM(文档对象模型)和无障碍树中。辅助技术可以访问可及性树的节点以了解:
- 通过检查它的角色,例如复选框,了解它是什么类型的元素。
- 该元素处于什么状态,例如,选中/未选中。
- 元素的名称,例如,"注册我们的通讯"。
使用HTML元素时,你得到的另一个东西是键盘交互性。例如,一个复选框可以用tab键聚焦,用空格键选择(具体的交互方式可能因浏览器和操作系统而异,但关键是当你使用HTML元素时,它们在所有网站上都是可用的,而且是标准化的)。
当你不使用HTML时,例如,如果你使用<div>
s和<span>
s建立你自己的自定义选择,或者你使用一个组件库,你需要做额外的工作来提供元素的信息,并为辅助技术用户建立键盘交互性。这就是ARIA发挥作用的地方。
ARIA
可访问的富互联网应用(ARIA)包括一组角色和属性,定义了使残疾人更容易访问网络内容和网络应用的方法。
你可以使用ARIA将信息传递给可访问性树。ARIA角色和属性不包括任何键盘互动性。<div>
将role="button”
,并不能使它在你按下回车键时作出反应--这一点你必须用JavaScript或其他语言来构建。然而,《ARIA创作实践指南》中确实包括了一个列表,列出了应该在各种组件(如手风琴、按钮、旋转木马等)中添加哪些键盘交互功能。
角色
让我们从角色开始。下面的代码中的这个东西到底是什么?
<div className="dd-wrapper">
<div className="dd-header">
<div className="dd-header-title"></div>
</div>
<div className="dd-list">
<button className="dd-list-item"></button>
<button className="dd-list-item"></button>
<button className="dd-list-item"></button>
</div>
</div>
这实际上是我在网上找到的一个代码片段,来自React的一个选择元素。事实上,这个元素从代码中完全无法识别,这正是任何辅助技术都会遇到的问题--它无法告诉用户它是什么,也无法告诉用户如何与它互动,因为没有ARIA角色。
看看我们在这里能做什么:
<div className="dd-wrapper" role="listbox">
你可能不熟悉listbox
,但它是一种屏幕阅读器用户可以识别并知道如何互动的select
。现在你可以直接使用<select>
,你不必给它一个角色,因为它已经有一个DOM和可访问性树会识别的角色,但我知道这并不总是一个可行的选择。
角色告诉辅助技术用户这个东西是什么,所以要确保你使用正确的角色。一个按钮和一个横幅是非常不同的。选择一个与你所构建的组件的功能相匹配的角色。
关于ARIA角色,你应该知道的另一件事是,它们覆盖了一个HTML元素的固有角色:
<img role="button">
这不再是一个图像,而是一个按钮。这样做的理由非常少,除非你确切地知道你在做什么和为什么,否则我会远离覆盖现有的HTML角色。有许多其他的方法可以实现这一点,从可访问性和代码健壮性的角度来看,这些方法更有意义:
<button><img src="image.png" alt="Print" /></button>
<input type="image" src="image.png" alt="Print" />
<button style="background: url(image.png)" />Print</button>
如果你正在建立一个组件,你可以在《ARIA创作实践指南》中查找该组件的模式,其中包括关于使用何种角色的信息。你也可以在cdn网络文档中查找所有可用的角色。
总之,如果你正在构建的东西没有描述它的语义HTML标签(即使用<div>
或<span>
构建的任何交互式东西),它就需要有一个ARIA角色,以便辅助技术能够识别它是什么。
状态和属性(又称ARIA属性)
除了知道一个元素是什么之外,如果它有一个状态(例如:hidden
,disabled
,invalid
,readonly
,selected
, 等等)或者改变了状态(例如:checked
/not checked
,open
/closed
, 等等),你需要告诉辅助技术用户它的当前状态是什么,以及它的新状态何时改变。你也可以分享一个元素的某些属性。状态和属性之间的区别其实并不清楚,也不重要,所以我们就把它们称为属性。
下面是一些你可能需要使用的最常见的ARIA属性:
aria-checked
它与="true"
或="false"
一起使用,表示复选框和单选按钮当前是否被选中。aria-current
它与="true"
或="false"
一起使用,表示面包屑或分页中的当前页面。aria-describedby
它与元素的id一起使用,除了标签之外,还可以为表单字段添加更多的信息。aria-describedby
,可以用来为一个字段提供所需格式的例子,例如日期,或者为一个表单字段添加错误信息。
<label for="birthday">Birthday</label>
<input type="text" id="birthday" aria-describedby="date-format">
<span id="date-format">MM-DD-YYYY</span>
aria-expanded
它与="true"
或="false"
一起使用,表示按下一个按钮是否会显示更多内容。例子包括手风琴和带有子菜单的导航项目。
<button aria-expanded="false">Products</button>
这表示产品菜单将打开一个子菜单(例如,不同的产品类别)。如果你要这样编码。
<a href="/products/">Products</a>
你在设置一个期望,即它是一个链接,点击它将进入一个新的页面。如果它不会进入一个新的页面,但它实际上停留在同一个页面上,但会打开一个子菜单,这就是按钮加aria-expanded
,对辅助技术用户说的。<button>
和<a>
之间的简单区别,以及增加的aria-expanded
传达了很多关于如何与元素互动以及当你这样做时会发生什么:
aria-hidden
它与="true"
或="false"
一起使用,以隐藏一些可见的东西,但你不想让辅助技术用户知道它。使用它时要非常谨慎,因为很少有不希望呈现同等信息的情况。
我见过的一个有趣的用例是,一张卡片上的图片和卡片的文字标题都链接到同一个页面,但结构上是两个独立的链接。想象一下,在一个页面上有许多这样的卡片。对于一个屏幕阅读器用户来说,他们会听到每个链接读出两次。所以图片链接使用了aria-hidden="true"
。解决这个问题的理想方法是将这些链接合并成一个既有图片又有文字标题的链接,但现实生活中的编码并不总是理想的,你并不总是有这样的控制水平。
请注意,这违反了ARIA的第四条规则(我们稍后会提到),但它是以一种不破坏可访问性的方式进行的。如果没有更好的解决办法,并且你已经对辅助技术用户进行了测试,那么使用它时要非常谨慎:
aria-required
它与="true"
或="false"
一起使用,以表明一个表单元素是否必须在表单提交前填写。
如果你正在构建一个组件,你可以在《ARIA创作实践指南》中查找该组件的属性。mdn web docs涵盖了状态和属性以及ARIA角色。
请记住,所有这些ARIA属性都会告诉用户一些事情,但你仍然必须对你所告诉他们的事情进行编码。aria-checked="true"
实际上并没有检查复选框;它只是告诉用户复选框被检查了,所以最好是真的,否则你会使事情变得更糟,而不是更好的可访问性。aria-hidden="true"
是个例外,它将一个元素从可访问性树中移除,有效地将它从使用辅助技术的人看不到的地方隐藏起来。
所以现在我们知道如何使用ARIA来解释什么东西,它处于什么状态,以及它有什么属性。我最后要讲的是焦点管理。
焦点管理
网站或网络应用中的任何交互式内容都必须能够接受焦点。不是每个人都会使用鼠标、触控板或触摸屏来与网站互动。许多人使用他们的键盘或模拟键盘的辅助技术设备。这意味着,对于所有你能点击的东西,你也应该能够使用Tab键或方向键来到达它,并使用Enter键,有时还使用空格键来选择它。
如果你使用<div>
和<span>
来创建互动元素,你需要考虑三个概念:
- 你需要添加
tabindex="0"
,以便键盘或模拟器能够聚焦于它们。 - 对于任何接受键盘输入的东西,你需要添加一个事件监听器来监听按键。
- 你需要添加适当的角色,以便屏幕阅读器用户能够识别你所建立的元素。
请记住,本地HTML控件已经接受了键盘焦点和输入,并且有固有的角色。这正是你从非语义化的HTML中创建自定义元素时需要做的。
本-迈尔斯(Ben Myers)对把一个div
变成一个按钮进行了深入的研究,我将在这里分享他的例子的一部分。注意tabindex和角色:
<div tabindex="0" role="button" onclick="doSomething();">
Click me!
</div>
而且你需要JavaScript来监听按键:
const ENTER = 13;
const SPACE = 32;
// Select your button and store it in ‘myButton’
myButton.addEventListener('keydown', function(event) {
if (event.keyCode === ENTER || event.keyCode === SPACE) {
event.preventDefault(); // Prevents unintentional form submissions, page scrollings, the like
doSomething(event);
}
});
当涉及到确定要监听哪些键时,我建议在《ARIA创作实践指南》中查找你要构建的组件,并遵循键盘交互建议。
常见错误
在我的一生中看了很多代码,我看到一些可访问性错误被反复犯。下面是我发现的最常见的错误以及如何避免这些错误的清单。
使用一个引用不存在的ID的aria-labelledby
属性
例如,一个模态里有一个标题,但aria-labelledby
,是引用了其他不再存在的东西。这可能是被另一个开发者删除的东西,他没有意识到aria-labelledby
连接的存在。相反,模态标题可以是一个<h1>
,而aria-labelledby
可以引用<h1>
,或者你可以在模态打开时将焦点设置在<h1>
,只要同时使用role="dialog”
,屏幕阅读器用户就会知道发生了什么。尽量避免脆弱的结构,如果别人来编辑代码,会很容易损坏。
当模版打开时不把焦点移到模版上
我曾无数次看到一个屏幕阅读器用户在模态后面浏览页面,要么不知道模态已经打开,要么因为找不到模态的内容而感到困惑。有几种方法可以在模态中捕获焦点,但其中一种较新的方法是在<main>
的地标中添加inert
(当然,要确保模态不在<main>
内)。最近,Inert
在各浏览器中得到了更好的支持。要了解更多信息,请查阅Lars Magnus Klavenes的《使用inert
的无障碍模态对话框》。
添加重复HTML的角色
一般来说,像这样做<button role="button”>
是没有意义的。有一种情况下,这样做可能是有意义的。当使用list-style: none
,VoiceOver和Safari会移除list
元素的语义。这样做是故意的,因为如果没有向视力正常的用户表明内容是一个列表,为什么要告诉屏幕阅读器用户它是一个列表?如果你想覆盖这一点,你可以在<ul>
,添加一个明确的ARIArole="list"
。
Adrian Roselli说,一个没有样式的列表不被宣布为列表"......可能不是什么大问题,除非用户测试说你真的需要一个列表"。在这一点上我同意他的观点,但我还是要分享这个修正,以防你的用户测试显示它是有益的。
为每个元素添加tabindex="0"
有时,开发者开始使用屏幕阅读器,并认为标签是导航的唯一方式;因此,任何没有标签索引的东西都是不可访问的。这是不正确的。记住,如果你不知道如何使用屏幕阅读器,你就无法解决可用性问题。与一个日常的屏幕阅读器用户见面来解决这些问题。
在没有父角色的情况下使用子角色
例如,role="option"
必须有一个直接的父角色,即role="listbox"
:
<div role="listbox">
<ul>
<li role="option">
上面的代码是无效的,因为在父元素和子元素之间有一个<ul>
。这可以通过添加一个表现角色来解决,基本上可以从可访问性树中隐藏<ul>
,如<ul role="presentation”>
。
在导航中使用role="menu"
网站导航实际上是一个目录,而不是一个菜单。ARIA菜单不是用来做导航的,而是像桌面应用程序中的菜单那样的应用行为。相反,使用<nav>
,如果你有子导航链接,这些链接应该是隐藏的,直到按下一个按钮来显示它们:
<nav aria-label="Main menu">
<button aria-expanded="false">Products</button>
<ul hidden>
<li>Cat pyjamas</li>...
如果你想了解更多,Heydon Pickering在他的Smashing杂志文章中对构建可访问的菜单系统做了深入研究。
关于导航,在一个页面上多次使用<nav>
,而不给每个实例一个独特的标签,意味着屏幕阅读器用户必须探索每个导航区域才能找到他们要找的那个。在每个<nav>
,一个简单的aria-label
,将使它更容易:
<nav aria-label="Customer service">
<ul>
<li><a href="#">Help</a></li>
<li><a href="#">Order tracking</a></li>
<li><a href="#">Shipping & Delivery</a></li>
<li><a href="#">Returns</a></li>
<li><a href="#">Contact us</a></li>
<li><a href="#">Find a store</a></li>
</ul>
</nav>
如何验证ARIA
当你在浏览器中运行你的代码时,使用自动可及性检查器,如Axe或WAVE扩展。像Axe for Visual Studio Code或ESLint for JSX elements这样的可访问性检查器将在你写代码时检查你的代码。
用屏幕阅读器听你的代码。如果不在浏览器中运行代码以确保其正常工作,你就不会发送代码,而使用屏幕阅读器也可以进行同样的检查。NVDA在Windows中是免费的,VoiceOver内置于Macs和iPhone中。TalkBack内置于安卓手机中。
对辅助技术用户进行测试。我认为这对任何有无障碍预算的大型组织来说都是强制性的(他们都应该如此)。有一些公司可以招募辅助技术用户进行测试或为你进行用户测试,我工作的公司可以为用户测试提供2天的周转时间,由你主持或不主持,以支持无障碍测试的规模。
框架和组件库
如果你使用的是一个网络框架,让无障碍建设的工作更轻松的一个方法是使用一个内建有无障碍功能的组件库。我要补充说明的是,无障碍性可能很复杂,并不是所有声称是无障碍的东西都能被辅助技术用户真正使用。确保无障碍性的最好方法是经常与你正在建立的用户进行测试。
总结
希望这能为你揭开ARIA的神秘面纱。就像一种只有最精英的无障碍性极客才知道的秘密语言,它有自己的搏击俱乐部式的规则:
- ARIA的第一条规则是 "不要使用ARIA"。一个
<button>
,永远比<div role="button">
。 - 第二,不要覆盖本地语义。不使用
<button role="heading">
,而使用<h3><button>
。 - 另外,永远记住,所有的ARIA交互式元素必须与键盘配合使用。
- 不要在一个可关注的元素上使用
role="presentation"
或aria-hidden="true"
。<button role="presentation”>
意味着你只对辅助技术用户隐藏该按钮。这不仅仅是无法使用,而是直接排除了某些用户。 - 最后但并非最不重要的是,所有互动元素都必须有一个无障碍的名称。有很多方法可以做到这一点,这里有一些。
<button>Print</button> (the name is the button text)
<div aria-label="Settings"><svg></div> (the aria-label assigns a name)
<div aria-labelledby="myName">
<h1 id="myName">Heading</h1>
</div>
<label for="name">Name</label>
<input type="text" id="name" />
我喜欢把ARIA想成是由最精锐的特别行动小组使用的工具,你可以叫他们来处理你最困难的无障碍挑战。好吧,也许我只是一直想做单臂俯卧撑,就像《明日边缘》中的艾米莉-布朗特那样,而这是我最接近的一次。总之,我希望这对你有帮助,你不再对ARIA感到困惑。去吧,建造可访问的东西