如何构建真正可用的可访问自定义下拉框(Combobox)

发布: (2026年4月23日 GMT+8 17:20)
5 分钟阅读
原文: Dev.to

Source: Dev.to

最初发表于 AccessGuard 博客

第一步:使用正确的角色

Combobox 不仅仅是一个经过样式化的按钮。WAI‑ARIA 作者实践定义了特定的角色结构:

  • 一个 combobox 元素(通常是 button 或 input),其 aria-haspopup="listbox" 并且 aria-expanded 反映打开状态。
  • aria-controls 指向 listbox 的 id。
  • 弹出层本身使用 role="listbox"
  • 每个选项使用 role="option",并在当前活动的选项上添加 aria-selected

提示: 角色必须与行为相匹配。不要在表现为菜单的 div 上随意加上 role="listbox"

步骤 2:使触发器可访问且具描述性

  • 触发器必须是一个真正可聚焦的元素——可以是原生的 或带有 `tabindex="0"` 的
  • 使用可见标签、aria-labelaria-labelledby 为其提供可访问名称。
  • 宣布当前值,使屏幕阅读器用户听到 “Country, combobox, United States”,而不仅仅是 “combobox, collapsed”。

第3步:键盘支持不可妥协

至少需要:

  • EnterSpace – 打开列表框。
  • Arrow Down – 打开(如果已关闭)并移动到第一个或已选中的选项。
  • Arrow Up / Arrow Down – 在选项之间移动。
  • Home / End – 跳转到第一个和最后一个选项。
  • Escape – 关闭并将焦点返回到触发器。
  • Tab – 关闭并移动到下一个可聚焦元素。
  • Typeahead – 按下字母键可跳转到下一个匹配的选项。

第4步:管理焦点,而不仅仅是视觉高亮

在列表框模式中,DOM 焦点保持在组合框触发器上。
在触发器上使用 aria-activedescendant,指向当前高亮选项的 id
这使得 typeahead 和键盘处理仍在触发器上,同时向辅助技术告知哪个选项是活动的。
将真实焦点移入列表是常见错误,会破坏 typeahead 并使屏幕阅读器感到困惑。

第5步:在不产生噪音的情况下宣布更改

当列表框打开时,如果 aria-expandedaria-activedescendant 正确连接,屏幕阅读器应自动宣布展开状态和活动选项。
避免使用额外的 aria-live 区域来重复宣布——双重宣布比没有宣布更糟。

Step 6: Do not forget the close behaviors

  • Escape 键、点击外部以及组合框失焦时关闭列表框。
  • 通过 Escape 关闭时,将焦点恢复到触发器。
  • 当用户选择一个选项时:
    1. 更新触发器的可见文本及其可访问名称。
    2. 关闭列表框。
    3. 将焦点返回到触发器。

快速测试清单

  • 只能使用键盘就能打开、导航、选择并关闭吗?
  • VoiceOver 或 NVDA 是否会朗读角色、状态和当前选项?
  • 输入提示(typeahead)是否有效?
  • 按 Escape 是否总是将焦点返回到触发元素?
  • 移动端的触摸用户是否能获得可用的体验?(提示:在 iOS 上使用 TalkBack 和 VoiceOver 进行测试)

如果你对所有这些都能回答 ,那么你已经超越了网络上 90 % 的自定义下拉菜单。

何时可以跳过所有这些

老实说,只要可能就使用原生的 “ 元素。它具有可访问性,兼容所有平台,且移动浏览器还能免费提供出色的选择器。

仅在确实需要原生 “ 无法提供的功能时才构建自定义组合框——可搜索的选项、丰富的选项内容或异步加载。最好的可访问组件往往是你根本不需要构建的那个。

我们将在后续发布关于可访问自动完成的文章,这会在此模式之上增加另一层复杂性。如果有你希望我们下次讨论的棘手组件,请告诉我们。

在 AccessGuard 上阅读更多内容,访问 getaccessguard.com

0 浏览
Back to Blog

相关文章

阅读更多 »