ARIA:选项卡角色

ARIA 的 tab 角色指示 tablist 中的一个交互式元素,当激活时,会显示其关联的 tabpanel

html
<button role="tab" aria-selected="true" aria-controls="tabpanel-id" id="tab-id">
  Tab label
</button>

描述

具有 tab 角色的元素控制具有 tabpanel 角色的关联元素的可见性。常见的用户体验模式是在内容区域的上面或旁边有一组可视标签,选择不同的标签会更改内容并使选定的标签比其他标签更突出。

具有 tab 角色的元素必须是具有 tablist 角色的元素的子元素,或者其 idtablistaria-owns 属性的一部分。这种组合向辅助技术标识元素属于一组相关元素。一些辅助技术将提供 tablist 中的 tab 角色元素的数量,并告知用户他们当前定位了哪个 tab。此外,具有 tab 角色的元素应该包含 aria-controls 属性,该属性通过该元素的 id 标识相应的 tabpanel(具有 tabpanel 角色)。当具有 tabpanel 角色的元素获得焦点,或者其子元素获得焦点时,这表示连接的具有 tab 角色的元素是 tablist 中的活动选项卡。

当具有 tab 角色的元素被选中或处于活动状态时,它们的 aria-selected 属性应设置为 true。否则,它们的 aria-selected 属性应设置为 false。当单个可选择 tablist 被选中或处于活动状态时,其他选项卡面板的 hidden 属性应设置为 true,直到用户选择与该选项卡面板关联的选项卡。当多个可选择 tablist 被选中或处于活动状态时,其对应的受控 tabpanel 应将其 aria-expanded 属性设置为 true,并将其 hidden 属性设置为 false,否则反之。

所有后代都是演示性的

在平台无障碍 API 中表示时,某些类型的用户界面组件只能包含文本。无障碍 API 没有方法来表示 tab 中包含的语义元素。为了解决此限制,浏览器会自动将角色 presentation 应用于任何 tab 元素的所有后代元素,因为这是一个不支持语义子元素的角色。

例如,考虑以下 tab 元素,它包含一个标题。

html
<div role="tab"><h3>Title of my tab</h3></div>

由于 tab 的后代是演示性的,因此以下代码等效

html
<div role="tab"><h3 role="presentation">Title of my tab</h3></div>

从辅助技术用户的角度来看,标题不存在,因为之前的代码片段在 无障碍树 中等效于以下内容

html
<div role="tab">Title of my tab</div>

关联的角色和属性

aria-selected

布尔值

aria-controls

具有 tabpanel 角色的元素的 id

id

内容

键盘交互

操作
Tab 当焦点在 tablist 之外时,将焦点移动到活动选项卡。如果焦点在活动选项卡上,则将焦点移动到键盘焦点顺序中的下一个元素,理想情况下是活动选项卡的关联 tabpanel
聚焦并可选地激活选项卡列表中的下一个选项卡。如果当前选项卡是选项卡列表中的最后一个选项卡,则会激活第一个选项卡。
聚焦并可选地激活选项卡列表中的上一个选项卡。如果当前选项卡是选项卡列表中的第一个选项卡,则会激活最后一个选项卡。
Delete 如果允许,则会从选项卡列表中删除当前选定的选项卡。

所需的 JavaScript 功能

注意:虽然有一些方法可以在没有 JavaScript 的情况下构建类似选项卡的功能,但没有使用仅 HTML 和 CSS 的组合可以替代上述为具有内容的可访问选项卡所需的功能集。

示例

此示例将tab角色与tablist和带有tabpanel元素结合使用,以创建一个交互式选项卡内容组。在这里,我们将内容组放在一个div中,我们的tablist具有aria-label,用于辅助技术的标签。每个tab都是一个带有前面提到的属性的button。第一个tab同时具有tabindex="0"aria-selected="true"属性。这两个属性必须始终协调一致,因此当选择另一个选项卡时,它将拥有tabindex="0"aria-selected="true"属性。所有未选中的选项卡必须具有aria-selected="false"tabindex="-1"

所有tabpanel元素都具有tabindex="0",以使它们可制表,除了当前活动元素之外,所有元素都具有hidden属性。当tabpanel通过 JavaScript 变得可见时,hidden属性将被删除。应用了一些基本的样式,这些样式重新设计按钮,并更改了z-index of tab 元素,以使活动元素的tab元素连接到tabpanel的错觉,以及非活动元素位于活动tabpanel后面的错觉。

html
<div class="tabs">
  <div role="tablist" aria-label="Sample Tabs">
    <button
      role="tab"
      aria-selected="true"
      aria-controls="panel-1"
      id="tab-1"
      tabindex="0">
      First Tab
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel-2"
      id="tab-2"
      tabindex="-1">
      Second Tab
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel-3"
      id="tab-3"
      tabindex="-1">
      Third Tab
    </button>
  </div>
  <div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
    <p>Content for the first panel</p>
  </div>
  <div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
    <p>Content for the second panel</p>
  </div>
  <div id="panel-3" role="tabpanel" tabindex="0" aria-labelledby="tab-3" hidden>
    <p>Content for the third panel</p>
  </div>
</div>

我们需要使用 JavaScript 完成两件事:我们需要使用左右箭头更改tab元素的焦点和选项卡索引,并且我们需要在点击tab时更改活动tabtabpanel

为了完成第一个,我们监听tablist上的keydown事件。如果事件的keyArrowRightArrowLeft,我们将对此事件做出反应。我们首先将当前tab元素的tabindex设置为 -1,使其不再可制表。然后,如果按下右箭头,我们将选项卡焦点计数器增加 1。如果计数器大于我们拥有的tab元素数量,我们将通过将该计数器设置为 0 来循环回到第一个选项卡。如果按下左箭头,我们将选项卡焦点计数器减少 1,如果它小于 0,我们将它设置为选项卡元素数量减 1(以到达最后一个元素)。最后,我们将focus设置为索引等于选项卡焦点计数器的tab元素,并将它的tabindex设置为 0,使其可制表。

为了处理更改活动tabtabpanel,我们有一个函数,它接收事件,获取触发事件的元素,触发元素的父元素及其祖父母元素。然后,我们找到父元素中所有具有aria-selected="true"的选项卡,将其设置为false,然后将触发元素的aria-selected设置为true。之后,我们在祖父母元素中找到所有tabpanel元素,使它们全部hidden,最后选择id等于触发tabaria-controls的元素,并删除hidden属性,使其可见。

js
window.addEventListener("DOMContentLoaded", () => {
  // Only handle one particular tablist; if you have multiple tab
  // lists (might even be nested), you have to apply this code for each one
  const tabList = document.querySelector('[role="tablist"]');
  const tabs = tabList.querySelectorAll(':scope > [role="tab"]');

  // Add a click event handler to each tab
  tabs.forEach((tab) => {
    tab.addEventListener("click", changeTabs);
  });

  // Enable arrow navigation between tabs in the tab list
  let tabFocus = 0;

  tabList.addEventListener("keydown", (e) => {
    // Move right
    if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
      tabs[tabFocus].setAttribute("tabindex", -1);
      if (e.key === "ArrowRight") {
        tabFocus++;
        // If we're at the end, go to the start
        if (tabFocus >= tabs.length) {
          tabFocus = 0;
        }
        // Move left
      } else if (e.key === "ArrowLeft") {
        tabFocus--;
        // If we're at the start, move to the end
        if (tabFocus < 0) {
          tabFocus = tabs.length - 1;
        }
      }

      tabs[tabFocus].setAttribute("tabindex", 0);
      tabs[tabFocus].focus();
    }
  });
});

function changeTabs(e) {
  const targetTab = e.target;
  const tabList = targetTab.parentNode;
  const tabGroup = tabList.parentNode;

  // Remove all current selected tabs
  tabList
    .querySelectorAll(':scope > [aria-selected="true"]')
    .forEach((t) => t.setAttribute("aria-selected", false));

  // Set this tab as selected
  targetTab.setAttribute("aria-selected", true);

  // Hide all tab panels
  tabGroup
    .querySelectorAll(':scope > [role="tabpanel"]')
    .forEach((p) => p.setAttribute("hidden", true));

  // Show the selected panel
  tabGroup
    .querySelector(`#${targetTab.getAttribute("aria-controls")}`)
    .removeAttribute("hidden");
}

最佳实践

建议使用<button>元素,其角色为tab,以实现其内置的功能和可访问性功能,而不是需要自己添加它们。为了控制具有tab角色的元素的选项卡键功能,建议将所有非活动元素设置为tabindex="-1",并将活动元素设置为tabindex="0"

优先级顺序

相关的属性是什么,以及此属性或属性的读取顺序,哪个属性优先于此属性,哪个属性将被覆盖。

规范

规范
可访问的富互联网应用 (WAI-ARIA)
# tab
未知规范

另请参见