让禁用按钮更具包容性

Avatar of Sandrina Pereira
Sandrina Pereira

DigitalOcean 提供适用于您旅程各个阶段的云产品。从 免费赠送的 200 美元信用额度 开始!

让我们来谈谈禁用按钮。具体来说,让我们深入了解一下为什么我们要使用它们,以及如何比 HTML 中传统的 disabled 属性(例如 <button disabled>)做得更好,以标记按钮为禁用状态。

在许多用例中,禁用按钮非常有意义,我们将在稍后讨论这些原因。但在这篇文章中,我们将查看一个允许我们向购物车添加一定数量票证的表单。

这是一个很好的基线示例,因为“添加到购物车”按钮有一个明确的禁用情况:当没有票证可以添加到购物车时。

但首先,为什么禁用按钮?

阻止人们执行无效或不可用的操作是使用禁用按钮的最常见原因。在下面的演示中,我们只有在添加到购物车的票证数量大于零时才能添加票证。试试看

请允许我跳过此演示中的代码解释,并将注意力集中在重要的事情上:“添加到购物车”按钮。

<button type="submit" disabled="disabled">
  Add to cart
</button>

此按钮被 disabled 属性禁用。(请注意,这是一个布尔属性,这意味着它可以写成 disableddisabled="disabled"。)

一切看起来都很好……那么有什么问题呢?

说实话,我可以在此结束这篇文章,要求您不要使用禁用按钮,因为它们很糟糕,而应该使用更好的模式。但让我们现实一点:有时禁用按钮是最合理的解决方案。

话虽如此,出于演示目的,我们将假装禁用“添加到购物车”按钮是最佳解决方案(**剧透警报:**不是)。我们仍然可以使用它来学习它的工作原理,并在此过程中改进其可用性。

交互类型

我想澄清一下,我所说的禁用按钮可用是什么意思。你可能会想,“如果按钮被禁用,它就不应该可用,那么……有什么问题呢?”请耐心听我解释。

在网络上,有多种与页面交互的方式。使用鼠标是最常见的一种,但还有其他方式,例如视力正常的人由于运动障碍而使用键盘进行导航。

尝试仅使用 Tab 键向前导航,使用 Tab + Shift 向后导航,来浏览上面的演示。您会注意到,禁用的按钮被跳过了。焦点直接从票证输入转移到“虚拟条款”链接。

使用 Tab 键,焦点从输入更改为链接,跳过“添加到购物车”按钮。

让我们暂停一下,回顾一下导致我们禁用按钮的原因,以及我们实际实现的效果。

通常将“交互”与“点击”联系在一起,但它们是两个不同的概念。是的,点击是一种交互方式,但它只是众多交互方式中的一种,例如悬停和聚焦。

换句话说……

所有点击都是交互,但并非所有交互都是点击。

我们的目标是阻止点击,但使用 disabled,我们不仅阻止了点击,还阻止了聚焦,这意味着我们可能造成的伤害与益处一样多。当然,这种行为可能看起来无害,但它会导致混淆。认知障碍的人可能难以理解为什么他们无法聚焦按钮。

在下面的演示中,我们稍微调整了一下布局。如果您使用鼠标并在提交按钮上悬停,就会显示一个工具提示,解释为什么按钮被禁用。太好了!

但是,如果您只使用键盘,则无法看到该工具提示,因为按钮无法使用 disabled 聚焦。在触摸设备中也会发生同样的事情。糟糕!

使用鼠标,“添加到购物车”按钮上的工具提示在悬停时可见。但使用 Tab 键时,工具提示不见了。

请允许我再次跳过代码解释。我强烈建议阅读 Haydon Pickering 的“包容性工具提示” 和 Sarah Higley 的“WCAG 2.1 时代的工具提示”,以充分了解工具提示模式。

ARIA 助攻

<button> 中的 disabled 属性功能过于强大。我知道的极少数情况下,原生 HTML 属性弊大于利,这便是其中之一。使用 ARIA 属性可以更好地完成任务,使我们能够指示屏幕阅读器如何解释按钮,但要始终如一地这样做,才能为更多人和其他用例创造一种包容性的体验。

disabled 属性对应于 aria-disabled="true"。再次尝试下面的演示,只使用键盘。请注意,尽管按钮被标记为禁用,但它仍然可以通过焦点访问,并且会触发工具提示!

使用 Tab 键,“添加到购物车”按钮会获得焦点,并显示工具提示。

很酷吧?小小的调整,带来巨大的改进!

但我们还没有完全结束。这里需要注意的是,我们仍然需要使用 JavaScript 以编程方式阻止点击。

elForm.addEventListener('submit', function (event) {
  event.preventDefault(); /* prevent native form submit */

  const isDisabled = elButtonSubmit.getAttribute('aria-disabled') === 'true';

  if (isDisabled || isSubmitting) {
    // return early to prevent the ticket from being added
    return;
  }

  isSubmitting = true;
  // ... code to add to cart...
  isSubmitting = false;
})

您可能熟悉这种模式,它可以防止双击两次提交表单。如果您出于此原因使用 disabled 属性,我更倾向于不这样做,因为这会导致表单提交时暂时失去键盘焦点。

disabledaria-disabled 之间的区别

您可能会问:如果 aria-disabled 默认情况下不阻止点击,那么使用它的意义何在?要回答这个问题,我们需要了解这两个属性之间的区别

功能 / 属性disabledaria-disabled="true"
阻止点击
阻止悬停
阻止焦点
默认 CSS 样式
语义

两者之间唯一的重叠是语义。这两个属性都会宣布按钮确实已禁用,这很好。

disabled 属性相反,aria-disabled 完全是关于语义的。ARIA 属性默认情况下永远不会更改应用程序行为或样式。它们唯一的目的是帮助辅助技术(例如屏幕阅读器)以更有意义和更健壮的方式宣布页面内容。

到目前为止,我们已经讨论了两种类型的人,点击的人和使用 Tab 键的人。现在,让我们讨论另一种类型:那些有视力障碍(例如失明、弱视)并使用屏幕阅读器浏览网络的人。

使用屏幕阅读器的人通常更喜欢使用 Tab 键导航表单字段。现在,看看 macOS 上的 VoiceOver 如何完全跳过 disabled 按钮。

VoiceOver 屏幕阅读器在使用 Tab 键时会跳过“添加到购物车”按钮。

再次强调,这是一个非常小的表单。在一个更长的表单中,立即找不到提交按钮会让人很烦躁。想象一个表单,其中提交按钮被隐藏,只有在您完全填写表单时才会显示。这就是有些人使用 disabled 属性时所感受的。

幸运的是,使用 disabled 的按钮不会完全无法被屏幕阅读器访问。您仍然可以逐个单独导航每个元素,最终会找到按钮。

VoiceOver 屏幕阅读器能够找到并宣布“添加到购物车”按钮。

虽然这是一种可行的做法,但这是一种让人恼火的体验。另一方面,使用 aria-disabled,屏幕阅读器会像往常一样聚焦按钮,并正确地宣布其状态。请注意,不同屏幕阅读器之间的宣布方式略有不同。例如,NVDA 和 JWAS 会说“按钮,不可用”,但 VoiceOver 会说“按钮,变暗”。

VoiceOver 屏幕阅读器可以找到使用 Tab 键的“添加到购物车”按钮,因为使用了 aria-disabled

我已经绘制了这两个属性基于我们刚刚使用的工具创建的不同用户体验的映射

工具 / 属性disabledaria-disabled="true"
鼠标或轻触阻止按钮点击。需要 JS 来阻止点击。
Tab无法聚焦按钮。能够聚焦按钮。
屏幕阅读器按钮难以定位。按钮很容易找到。

因此,这两个属性之间主要区别是

  • disabled 可能会在使用 Tab 键时跳过按钮,从而导致混乱。
  • aria-disabled 仍然会聚焦按钮并宣布它的存在,但它尚未启用;就像您在视觉上感知它一样。

这就是承认可访问性和可用性之间细微差别的重要之处。可访问性衡量的是某人是否能够使用某物。可用性衡量的是某物使用起来有多容易。

鉴于此,disabled 是否可访问?是的。它的可用性好吗?我不这么认为

我们可以做得更好吗?

如果我在不向您展示我们门票表单示例的真正包容性解决方案的情况下结束这篇文章,我会感到不安。尽可能不要使用禁用按钮。让用户随时点击它,如果需要,显示错误消息作为反馈。这种方法还可以解决其他问题

  • 更少的认知摩擦:允许用户随时提交表单。这消除了按钮是否被禁用的不确定性。
  • 颜色对比度:虽然禁用按钮不需要满足 WCAG 1.4.3 颜色对比度,但我认为我们应该保证元素始终可见,无论其状态如何。但现在我们不必担心这个问题,因为按钮不再被禁用。

最后的想法

<button> 中的 disabled 属性是一个奇怪的情况,尽管它在社区中广为人知,但它可能不是解决特定问题的最佳方法。不要误解我,因为我不是说 disabled 总是不好的。仍然有一些情况下使用它是有意义的(例如分页)。

说实话,我不认为 disabled 属性完全是一个可访问性问题。让我更担心的是可用性问题。通过用 aria-disabled 替换 disabled 属性,我们可以使用户的体验更加愉快。

这只是我迈向网络可访问性之旅的又一步。多年来,我发现可访问性远不止遵守网络标准。处理用户体验很棘手,大多数情况下需要权衡和妥协我们在如何处理解决方案方面的方法。没有完美的可访问性的灵丹妙药。

我们作为网络创建者的职责是寻找和理解可用的不同解决方案。只有这样,我们才能做出最好的选择。假装问题不存在毫无意义。

最后,请记住,没有什么能阻止您使网络更具包容性。

奖励

还在吗?让我提一下关于这个演示的最后两件事,我认为它们很有价值

1. 实时区域将宣布动态内容

在演示中,内容的两个部分发生了动态变化:表单提交按钮和成功确认(“已添加 [X] 张票!”)。

这些变化在视觉上是可以感知的,但是,对于使用屏幕阅读器的视障人士来说,这并不是现实。为了解决这个问题,我们需要将这些消息变成实时区域。这些允许辅助技术监听更改,并在更改发生时宣布更新的消息。

在演示中有一个 .sr-only,它隐藏了一个包含加载消息的 <span>,但允许屏幕阅读器宣布它。在这种情况下,aria-live="assertive" 应用于 <span>,并且它在表单提交后并处于加载过程中的时候保存一个有意义的消息。这样,我们可以向用户宣布表单正在工作,并请耐心等待加载。此外,我们对表单反馈元素也做同样的事情。

<button type="submit" aria-disabled="true">
  Add to cart
  <span aria-live="assertive" class="sr-only js-loadingMsg">
     <!-- Use JavaScript to inject the the loading message -->
  </span>
</button>

<p aria-live="assertive" class="formStatus">
  <!-- Use JavaScript to inject the success message -->
</p>

请注意,aria-live 属性必须从一开始就存在于 DOM 中,即使元素还没有保存任何消息,否则辅助技术可能无法正常工作。

表单提交反馈消息由屏幕阅读器宣布。

关于这个小小的 aria-live 属性以及它所做的大事情,还有更多内容要告诉你。也有一些陷阱。例如,如果它被错误地应用,它会弊大于利。值得阅读 Ire Aderinokun 的 “使用 aria-live” 以及 Adrian Roselli 的 “加载骨架”,以更好地了解它是如何工作的以及如何使用它。

2. 不要使用 pointer-events 来阻止点击

这是我在网上看到的一种替代(且不正确)的实现方式。它在 CSS 中使用 pointer-events: none; 来阻止点击(没有任何 HTML 属性)。请不要这样做。这是一个 丑陋的 Pen,希望能够证明为什么。我再说一遍,不要这样做。

尽管该 CSS 确实阻止了鼠标点击,但请记住,它不会阻止聚焦和键盘导航,这会导致意外结果,甚至更糟糕的是错误。

换句话说,使用此 CSS 规则作为阻止点击的策略是毫无意义的(明白了吗?)。;)