构建无障碍菜单系统

已发表: 2022-03-10
快速总结↬网络上有很多不同类型的菜单。 创造包容性体验是一个在正确的地方使用正确的菜单模式、正确的标记和行为的问题。

编者注本文最初出现在包容性组件上。 如果您想了解更多类似的包容性组件文章,请在 Twitter 上关注 @inclusicomps 或订阅 RSS 提要。 通过在 Patreon 上支持 inclusive-components.design,您可以帮助使其成为最全面的强大接口组件数据库。

分类很难。 以螃蟹为例。 寄居蟹、瓷蟹和马蹄蟹——从分类学上讲——不是真正的螃蟹。 但这并不能阻止我们使用“螃蟹”后缀。 随着时间的推移,由于一种称为“化”的过程,不真实的螃蟹进化成更接近真实的螃蟹时,它变得更加令人困惑。 帝王蟹就是这种情况,过去人们认为它们是寄居蟹。 想象一下它们贝壳的大小!

在设计中,我们经常犯同样的错误,给不同的东西取同一个名字。 它们看起来相似,但外观可能具有欺骗性。 这可能会对组件库的清晰度产生不利影响。 在包含方面,它还可能导致您重新利用语义和行为上不适当的组件。 用户会期待一件事并得到另一件事。

术语“下拉”命名了一个经典示例。 界面中有很多东西“下拉”,包括来自<select>元素的<option>集合,以及 JavaScript 显示的构成导航子菜单的链接列表。 一样的名字; 完全不同的东西。 (当然,有些人称这些为“下拉”,但我们不要深入探讨。)

构成一组选项的下拉菜单通常被称为“菜单”,我想在这里谈谈这些。 我们将设计一份真正的菜单,但在此过程中,关于非真正的菜单还有很多话要说。

跳跃后更多! 继续往下看↓

让我们从一个测验开始。 插图中导航栏上悬挂的链接框是菜单吗?

一个导航栏包括一个商店链接,在该链接下方挂着一组三个链接,分别指向狗服装、华夫饼铁和魔法球。
一个导航栏包括一个商店链接,在该链接下方挂着一组三个链接,分别指向狗服装、华夫饼铁和魔法球。 (大预览)

答案是否定的,不是真正的菜单。

导航模式由链接列表组成是一个长期的约定。 一个几乎长期存在的约定规定子导航应该作为嵌套的链接列表提供。 如果我要删除上面所示组件的 CSS,我应该会看到类似下面的内容,除了蓝色和 Times New Roman。

从语义上讲,嵌套的链接列表在这种情况下是正确的。 导航系统实际上是目录,这就是目录的结构。 唯一真正让我们想到“菜单”的是嵌套列表的样式以及它们在悬停或聚焦时显示的方式。

这就是一些错误并开始添加 WAI-ARIA 语义的地方: aria-haspopup="true"role="menu"role="menuitem"等。这些有一个地方,我们将介绍,但不是在这里. 这里有两个原因:

  1. ARIA 菜单不是为导航指定的,而是为应用程序行为指定的。 想象一下桌面应用程序的菜单系统。
  2. 顶级链接应该可以用作链接,这意味着它的行为不像菜单按钮。

关于(2):当遍历带有子菜单的导航区域时,人们会期望每个子菜单在悬停或聚焦“顶级”链接(图中的“商店”)时出现。 这既显示了子菜单,又按焦点顺序放置了自己的链接。 借助 JavaScript 捕获焦点和模糊事件以在需要时保持子菜单的外观的一点帮助,使用键盘的人应该能够依次通过每个层的每个链接进行选项卡。

采用aria-haspopup="true"属性的菜单按钮的行为不是这样的。 它们在点击时被激活,除了显示一个隐藏的菜单之外没有其他目的。

电子书折扣 bieten wir
左:标有“菜单”的菜单按钮,带有向下箭头图标和 aria-expanded = false 状态。 右:相同的菜单按钮,但菜单打开。 此按钮处于 aria-expanded = true 状态。 (大预览)

如图所示,该菜单是打开还是关闭应该与aria-expanded进行通信。 您应该只在单击时更改此状态,而不是在焦点上。 用户通常不期望仅在焦点事件上显式更改状态。 在我们的导航系统中,状态并没有真正改变。 这只是一个造型技巧。 在行为上,我们可以在导航中使用Tab ,就好像没有发生这种显示/隐藏技巧一样。

导航子菜单的问题

导航子菜单(或某些人的“下拉菜单”)可以很好地使用鼠标或键盘,但在触摸时它们并不那么热。 当您第一次按下我们示例中的顶级“Shop”链接时,您是在告诉它打开子菜单并点击链接。

这里有两种可能的解决方案:

  1. 在完整的 WAI-ARIA 菜单语义和行为中防止顶级链接 ( e.preventDefault() ) 和脚本的默认行为。
  2. 确保每个顶级目标页面都有一个目录作为子菜单的替代项。

(1) 不能令人满意,因为正如我之前提到的,在这种情况下,这些语义和行为是不期望的,其中链接是主题控件。 Plus 用户无法再导航到顶级页面(如果存在)。

旁注:哪些设备是触控设备?

很容易想到,“这不是一个很好的解决方案,但我只会为触摸界面添加它”。 问题是:如何检测设备是否有触摸屏?

您当然不应该将“小屏幕”与“触摸激活”等同起来。 与为博物馆制作触摸屏的人在同一个办公室工作过,我可以向您保证,周围一些最大的屏幕是触摸屏。 双键盘和触摸输入笔记本电脑也越来越多产。

同样,许多但并非所有较小的设备都是触摸设备。 在包容性设计中,您不能做出假设。

解决方案 (2) 更具包容性和稳健性,因为它为所有输入的用户提供了“后备”。 但是这里的后备术语周围的吓人引用是经过深思熟虑的,因为我实际上认为页内目录是提供导航的一种更好的方式。

屡获殊荣的政府数字服务团队似乎同意这一点。 您可能还曾在 Wikipedia 上看到过它们。

Gov.uk 目录很少,以连字符作为列表样式。维基百科提供了一个带有编号项目的带边框的灰色框。两者都是标记的内容。
Gov.uk 目录很少,以连字符作为列表样式。 维基百科提供了一个带有编号项目的带边框的灰色框。 两者都是标记的内容。

目录

目录是相关页面或页面部分的导航,在语义上应该类似于主站点导航区域,使用<nav>元素、列表和组标签机制。

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->

笔记

  • 在这个例子中,我们假设每个部分都是它自己的页面,就像它在下拉子菜单中一样。
  • 重要的是,这些“商店”页面中的每一个都具有相同的结构,并且此“产品”目录出现在同一个位置。 一致性支持理解。
  • 该列表将项目分组并在辅助技术输出中枚举它们,例如屏幕阅读器的合成语音。
  • <nav>使用aria-labelledby labelledby 由标题递归标记。 这意味着“产品导航”将在通过Tab进入区域时在大多数屏幕阅读器中公布。 这也意味着“产品导航”将在屏幕阅读器元素界面中逐项列出,用户可以从中直接导航到区域。

全部在一页上

如果您可以将所有部分放在一页上,而不会变得太长且难以滚动,那就更好了。 只需链接到每个部分的哈希标识符。 例如, href="#waffle-irons"应该指向.

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->

注意:有些浏览器在将焦点实际发送到链接的页面片段方面很差。将tabindex="-1"放在目标片段上可以解决这个问题。)

当一个网站有很多内容时,一个精心构建的信息架构,通过自由使用目录“菜单”来表达,比不稳定和笨拙的下拉系统更可取。 不仅更容易做出响应,并且需要更少的代码来做到这一点,而且它使事情变得更清晰:下拉系统隐藏结构的地方,目录将其暴露。

一些网站,包括政府数字服务的 gov.uk,包括只是目录的索引(或“主题”)页面。 这是一个非常强大的概念,流行的静态站点生成器 Hugo 默认会生成这样的页面。

家谱样式图,顶部有主题登录页面,有两个单独的页面分支。每个单独的页面分支都有多个页面部分分支
家谱样式图,顶部有主题登录页面,有两个单独的页面分支。 每个单独的页面分支都有多个页面部分分支(大预览)

信息架构是包容性的重要组成部分。 一个组织不善的网站在技术上可以随心所欲,但仍会疏远很多用户——尤其是那些有认知障碍或时间紧迫的用户。

导航菜单按钮

虽然我们讨论的是与导航相关的人造菜单,但如果我不谈论导航菜单按钮,那就太失职了。 您几乎肯定已经看到这些由三行“汉堡包”或“navicon”图标表示。

即使使用精简的信息架构和只有一层导航链接,小屏幕上的空间也是非常宝贵的。 将导航隐藏在按钮后面意味着视口中的主要内容有更多空间。

导航按钮是迄今为止我们研究过的最接近真正菜单按钮的东西。 由于它的目的是在点击时切换菜单的可用性,它应该:

  1. 将自己标识为按钮,而不是链接;
  2. 识别其相应菜单的展开或折叠状态(严格来说,它只是一个链接列表)。

渐进式增强

但是,我们不要超越自己。 我们应该注意渐进增强,并考虑在没有 JavaScript 的情况下这将如何工作。

在未增强的 HTML 文档中,您可以使用按钮做的事情不多(提交按钮除外,但这与我们在这里想要实现的目标没有密切关系)。 相反,也许我们应该从一个带我们到导航的链接开始?

 <a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

除非链接和导航之间有很多内容,否则拥有链接并没有多大意义。 由于站点导航应该几乎总是出现在源订单的顶部附近,因此没有必要。 所以,真的,没有 JavaScript 的导航菜单应该只是……一些导航。

 <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

您可以通过添加处于初始状态的按钮并隐藏导航(使用hidden属性)来增强此功能:

 <nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

一些较旧的浏览器——你知道哪些——不支持hidden ,所以记得在你的 CSS 中加入以下内容。 它解决了这个问题,因为display: none具有从辅助技术隐藏菜单和从焦点顺序中删除链接的相同效果。

 [hidden] { display: none; }

尽最大努力支持旧软件当然是一种包容性设计行为。 有些无法或不愿意升级。

放置

很多人出错的地方是将按钮放在区域之外。 这意味着使用快捷方式移动到<nav>的屏幕阅读器用户会发现它是空的,这不是很有帮助。 在屏幕阅读器隐藏列表的情况下,他们只会遇到以下情况:

 <nav> </nav>

以下是我们如何切换状态:

 var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });

咏叹调控制

正如我在 Aria-controls Is Poop 中所写,旨在帮助屏幕阅读器用户从控制元素导航到受控元素的aria-controls属性仅在 JAWS 屏幕阅读器中受支持。 所以你根本不能依赖它。

如果没有一种在元素之间引导用户的好方法,您应该确保满足以下条件之一:

  1. 展开列表的第一个链接按焦点顺序位于按钮之后(如前面的代码示例中所示)。
  2. 第一个链接以编程方式集中在显示列表上。

在这种情况下,我会推荐(1)。 这要简单得多,因为您不必担心将焦点移回按钮以及要这样做的事件。 此外,目前没有任何东西可以警告用户他们的注意力将转移到不同的地方。 在我们稍后将讨论的真正菜单中,这是aria-haspopup="true"的工作。

使用aria-controls并没有真正造成太大的危害,只是它会使屏幕阅读器中的读数更加冗长。 但是,一些 JAWS 用户可能会期待它。 以下是它的应用方式,使用列表的id作为密码:

 <nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

menu 和 menuitem 角色

一个真正的菜单(在 WAI-ARIA 意义上)应该使用menu角色(对于容器)和通常的menuitem子角色(可能适用其他子角色)来标识自己。 这些父子角色协同工作,为辅助技术提供信息。 以下是如何扩充列表以具有菜单语义的方法:

 <ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>

既然我们的导航菜单开始表现得有点像一个“真正的”菜单,这些不应该出现吗?

最简洁的答案是不。 长答案是:不,因为我们的列表项包含链接,并且menuitem元素不打算具有交互式后代。 也就是说,它们菜单中的控件。

当然,我们可以使用role="presentation"role="none" (它们是等效的)来抑制<li>的列表语义,并将menuitem角色放置在每个链接上。 但是,这会抑制隐式链接角色。 换句话说,要遵循的示例将被宣布为“主页,菜单项”,而不是“主页,链接”或“主页,菜单项,链接”。 ARIA 角色只是简单地覆盖 HTML 角色。

 <!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>

我们希望用户知道他们正在使用链接并且可以预期链接行为,所以这不好。 就像我说的,真正的菜单用于(JavaScript 驱动的)应用程序行为。

我们剩下的是一种混合组件,它不是一个真正的菜单,但至少告诉用户链接列表是否打开,这要归功于aria-expanded状态。 这是一个非常令人满意的导航菜单模式。

旁注: <select>元素

如果您从一开始就参与了响应式设计,您可能还记得一种模式,其中导航被压缩为用于窄视口的<select>元素。

带有选择元素的手机,显示在视口顶部选择的“家”
带有选择元素的手机,显示在视口顶部选择的“主页”。

与我们讨论的基于复选框的切换按钮一样,使用在没有额外脚本的情况下表现得有点像预期的本机元素是提高效率和(尤其是在移动设备上)性能的好选择。 <select>元素各种菜单,其语义与我们即将构建的按钮触发菜单相似。

然而,就像复选框切换按钮一样,我们使用与输入相关的元素,而不是简单地做出选择。 这可能会导致许多用户感到困惑——尤其是因为这种模式使用 JavaScript 来使选定的<option>表现得像一个链接。 根据 WCAG 的 3.2.2 输入(A 级)标准,由此引发的意外上下文变化被视为失败。

真正的菜单

既然我们已经讨论了假菜单和准菜单,现在是时候创建一个真正的菜单了,它由一个真正的菜单按钮打开和关闭。 从这里开始,我将按钮和菜单统称为“菜单按钮”。

但是我们的菜单按钮在哪些方面是真实的呢? 好吧,它将是一个菜单组件,用于在主题应用程序中选择选项,它实现了所有预期的语义和相应的行为,这些语义和相应的行为被认为是此类工具的常规行为。

如前所述,这些约定来自桌面应用程序设计。 需要 ARIA 归因和 JavaScript 控制的焦点管理来完全模仿它们。 ARIA 的部分目的是帮助 Web 开发人员创建丰富的 Web 体验,而不会打破在原生世界中形成的可用性约定。

在此示例中,我们将假设我们的应用程序是某种游戏或测验。 我们的菜单按钮将让用户选择难度级别。 有了所有的语义,菜单看起来像这样:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>

笔记

  • aria-haspopup属性只是表明按钮隐藏了一个菜单。 它作为警告,当按下时,用户将被移动到“弹出”菜单(我们将很快介绍焦点行为)。 它的价值不会改变——它始终true不变。
  • 按钮内的<span>包含黑色向下小三角形的 unicode 点。 这个约定在视觉上表明了aria-haspopup在非视觉上做了什么——按下按钮会在它下面显示一些东西。 aria-hidden="true"属性可防止屏幕阅读器宣布“向下三角形”或类似内容。 感谢aria-haspopup ,在非视觉环境中不需要它。
  • aria-haspopup属性由aria-expanded补充。 这通过在true值和false值之间切换来告诉用户菜单当前是处于打开(展开)还是关闭(折叠)状态。
  • 菜单本身具有(恰当命名的) menu角色。 它需要具有menuitem角色的后代。 它们不需要是menu元素的直接子元素,但在这种情况下它们是——为简单起见。

键盘和焦点行为

在使交互式控件键盘可访问时,您能做的最好的事情就是使用正确的元素。 因为我们在这里使用<button>元素,所以我们可以确保点击事件将在EnterSpace击键时触发,正如 HTMLButtonElement 界面中所指定的那样。 这也意味着我们可以使用与按钮相关的disabled属性来禁用菜单项。

不过,菜单按钮键盘交互还有很多。 以下是我们将根据 WAI-ARIA 创作实践 1.1 实现的所有焦点和键盘行为的摘要:

Enter , Space在菜单按钮上打开菜单
在菜单项上将焦点移至下一个菜单项,如果您在最后一个菜单项上,则将焦点移至第一个菜单项
在菜单项上将焦点移至上一个菜单项,如果您在第一个菜单项上,则将焦点移至最后一个菜单项
在菜单按钮上如果打开则关闭菜单
Esc在菜单项上关闭菜单并聚焦菜单按钮

使用箭头键在菜单项之间移动焦点的优点是保留Tab以移出菜单。 实际上,这意味着用户不必遍历每个菜单项即可退出菜单——这极大地提高了可用性,尤其是在有许多菜单项的情况下。

tabindex="-1"的应用使菜单项无法通过Tab聚焦,但保留了在捕获箭头键上的击键时以编程方式聚焦元素的能力。

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>

打开方法

作为合理 API 设计的一部分,我们可以构建处理各种事件的方法。

例如, open方法需要将aria-expanded值切换为“true”,将菜单的 hidden 属性更改为false ,并聚焦菜单中未禁用的第一个menuitem

 MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }

我们可以在用户按下焦点菜单按钮实例上的向下键时执行此方法:

 this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));

此外,使用此脚本的开发人员现在可以通过编程方式打开菜单:

 exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();

旁注:复选框黑客

除非需要,否则最好不要使用 JavaScript。 在 HTML 和 CSS 之上引入第三种技术必然会增加系统的复杂性和脆弱性。 但是,并非所有组件都可以在没有 JavaScript 的情况下令人满意地构建。

在菜单按钮的情况下,让它们“在没有 JavaScript 的情况下工作”的热情导致了所谓的复选框 hack。 这是隐藏复选框的选中(或未选中)状态用于使用 CSS 切换菜单元素的可见性的地方。

 /* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }

对于屏幕阅读器用户来说,复选框角色和选中状态在这种情况下是没有意义的。 这可以通过将role="button"添加到复选框来部分克服。

 <input type="checkbox" role="button" aria-haspopup="true">

不幸的是,这抑制了隐式检查状态通信,剥夺了我们无 JavaScript 的状态反馈(虽然在这种情况下它会被视为“检查”)。

但是可能欺骗aria-expanded 。 我们只需要为我们的标签提供两个跨度,如下所示。

 <input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded&lt;/span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">&#x25be;</span> </label>

这些都使用visually-hidden ,但是——取决于我们所处的状态——只有一个对屏幕阅读器是隐藏的。 也就是说,只有一个具有display: none ,这是由现存的(但未传达的)检查状态决定的:

 /* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }

这很聪明,但我们的菜单按钮仍然不完整,因为我们一直在讨论的预期焦点行为根本无法在没有 JavaScript 的情况下实现。

这些行为是常规的和预期的,使按钮更有用。 然而,如果你真的需要在没有 JavaScript 的情况下实现一个菜单按钮,这几乎是你能得到的。 考虑到我之前提到的缩减导航菜单按钮提供的菜单内容本身依赖于 JavaScript(即链接),这种方法可能是一个合适的选择。

为了好玩,这是一个实现无 JavaScript 导航菜单按钮的 codePen。

请参阅 CodePen 上 Heydon (@heydon) 编写的 Pen Navigation menu button example no JS。

注意:只有Space打开菜单。)

“选择”事件

执行一些方法应该发出事件,以便我们可以设置监听器。 例如,我们可以在用户单击菜单项时发出choose事件。 我们可以使用CustomEvent进行设置,它允许我们将参数传递给事件的detail属性。 在这种情况下,参数(“choice”)将是所选菜单项的 DOM 节点。

 MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }

我们可以用这个机制做各种各样的事情。 也许我们设置了一个活动区域,其idmenuFeedback

 <div role="alert"></div>

现在我们可以设置一个监听器并使用事件中隐藏的信息填充活动区域:

 exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
当用户选择一个选项时,菜单关闭并且焦点返回到菜单按钮。重要的是,用户在菜单关闭后返回到触发元素。
当用户选择一个选项时,菜单关闭并且焦点返回到菜单按钮。 重要的是用户在菜单关闭后返回到触发元素。 (大预览)

选择菜单项后,屏幕阅读器用户将听到“您选择了 [菜单项的标签]” 。 实时区域(此处定义为role=“alert”属性)在内容发生变化时在屏幕阅读器中宣布其内容。 实时区域不是强制性的,但它是界面中可能发生的一个示例,作为对用户做出菜单选择的响应。

坚持选择

并非所有菜单项都用于选择持久设置。 许多只是像标准按钮一样,在按下时会在界面中发生某些事情。 然而,在我们的难度菜单按钮的情况下,我们想指出哪个是当前难度设置——最后选择的那个。

aria-checked="true"属性适用于代替menuitem的项目,而不是menuitemradio角色。 选中第二项 ( set ) 的增强标记如下所示:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>

许多平台上的本机菜单使用复选标记指示所选项目。 我们可以使用一点额外的 CSS 轻松做到这一点:

 [role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }

在运行屏幕阅读器的情况下遍历菜单时,聚焦此选中项将提示类似“选中标记,中菜单项,选中”的通知。

打开带有选中menuitemradio的菜单的行为略有不同。 不是聚焦菜单中的第一个(启用的)项目,而是选中的项目聚焦。

菜单按钮以未打开的菜单开始。打开第二个(中等)难度设置的重点。它的前缀是基于 aria-checked 属性的存在的复选标记。
菜单按钮以未打开的菜单开始。 打开第二个(中等)难度设置的重点。 它的前缀是基于 aria-checked 属性的存在的复选标记。 (大预览)

这种行为有什么好处? 用户(任何用户)被提醒他们之前选择的选项。 在具有许多增量选项(例如,一组缩放级别)的菜单中,通过键盘操作的人员被放置在最佳位置以进行调整。

将菜单按钮与屏幕阅读器一起使用

在本视频中,我将向您展示在 Voiceover 屏幕阅读器和 Chrome 中使用菜单按钮的感受。 该示例使用带有menuitemradioaria-checked和讨论的焦点行为的项目。 在所有流行的屏幕阅读器软件中,都可以期待类似的体验。

Github 上的包容性菜单按钮

Kitty Giraudel 和我一起创建了一个菜单按钮组件,其中包含我所描述的 API 功能等等。 你要感谢 Hugo 的许多功能,因为它们是基于他们在 a11y-dialog 上所做的工作——一个可访问的模式对话框。 它在 Github 和 NPM 上可用。

 npm i inclusive-menu-button --save

此外,Kitty 还为您创建了一个 React 版本。

清单

  • 不要在导航菜单系统中使用 ARIA 菜单语义。
  • 在内容繁重的网站上,不要在嵌套的下拉导航菜单中隐藏结构。
  • 使用aria-expanded指示按钮激活的导航菜单的打开/关闭状态。
  • 确保所述导航菜单位于打开/关闭按钮之后的焦点顺序中。
  • 永远不要为了追求无 JavaScript 的解决方案而牺牲可用性。 是虚荣。