ARIA: tab 角色

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 属性的一部分。此组合向辅助技术表明该元素是一个相关元素组的一部分。一些辅助技术会提供 tablisttab 角色元素的计数,并告知用户他们当前已定位到哪个 tab。此外,具有 tab 角色的元素应该包含 aria-controls 属性,该属性通过其 id 标识一个对应的 tabpanel(具有 tabpanel 角色)。当具有 tabpanel 角色的元素获得焦点,或其子元素获得焦点时,这表明连接的具有 tab 角色的元素是 tablist 中的活动标签。

当具有 tab 角色的元素被选中或处于活动状态时,其 aria-selected 属性应设置为 true。否则,其 aria-selected 属性应设置为 false。当单选的 tablist 被选中或处于活动状态时,其他 tabpanel 的 hidden 属性应设置为 true,直到用户选择与该 tabpanel 关联的标签。当多选的 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

boolean

aria-controls

具有 tabpanel 角色的元素的 id

id

content

键盘交互

动作
制表符 当焦点移出 tablist 时,将焦点移至活动标签。如果焦点在活动标签上,则将焦点移至键盘焦点顺序中的下一个元素,最好是活动标签关联的 tabpanel
聚焦并可选地激活标签列表中的下一个标签。如果当前标签是标签列表中的最后一个标签,则激活第一个标签。
聚焦并可选地激活标签列表中的上一个标签。如果当前标签是标签列表中的第一个标签,则激活最后一个标签。
Enter/Space 当标签获得焦点时,激活该标签,使其关联的面板显示出来。
Home 聚焦并可选地激活标签列表中的第一个标签。
End 聚焦并可选地激活标签列表中的最后一个标签。
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 属性。

注意:如果 tab panel 中的第一个元素是可聚焦的(例如链接),则在 tab panel 上设置 tabindex 是不必要的,因为聚焦到该链接也会开始读取 panel 的内容。但是,如果集合中有任何 panel 的第一个内容元素不可聚焦,则 tab 集合中的所有 tabpanel 元素都应该是可聚焦的,以便屏幕阅读器用户能够一致地导航到 panel 内容。

html
<div class="tabs">
  <div role="tablist" aria-label="Select your operating system">
    <button
      role="tab"
      aria-selected="true"
      aria-controls="panel-1"
      id="tab-1"
      tabindex="0">
      Windows
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel-2"
      id="tab-2"
      tabindex="-1">
      macOS
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel-3"
      id="tab-3"
      tabindex="-1">
      Linux
    </button>
  </div>
  <div class="tab-panels">
    <div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
      <p>How to run this application on Windows</p>
    </div>
    <div
      id="panel-2"
      role="tabpanel"
      tabindex="0"
      aria-labelledby="tab-2"
      hidden="hidden">
      <p>How to run this application on macOS</p>
    </div>
    <div
      id="panel-3"
      role="tabpanel"
      tabindex="0"
      aria-labelledby="tab-3"
      hidden="hidden">
      <p>How to run this application on Linux</p>
    </div>
  </div>
</div>

应用了一些基本样式,这些样式重新设置了按钮的样式,并更改了 tab 元素的 z-index,以造成它与活动元素的 tabpanel 连接的错觉,并造成非活动元素位于活动 tabpanel 后面的错觉。您需要清楚地区分活动标签和非活动标签,例如使用更粗的边框或更大的尺寸。

用户交互由 JavaScript 处理。我们首先获取对 tablist、其中的所有 tab 元素、tabpanel 元素容器以及该容器中的所有 tabpanel 元素的引用。这基于我们对 HTML 结构的一些假设,因此如果您更改了结构,则需要更改此代码。如果您在一个页面上有多个标签式界面,您可以将此代码包装在一个函数中,并将 tabsContainer 作为参数传递。

js
const tabsContainer = document.querySelector(".tabs");
const tabList = tabsContainer.querySelector(':scope > [role="tablist"]');
const tabs = Array.from(tabList.querySelectorAll(':scope > [role="tab"]'));
const tabPanelsContainer = tabsContainer.querySelector(":scope > .tab-panels");
const tabPanels = Array.from(
  tabPanelsContainer.querySelectorAll(':scope > [role="tabpanel"]'),
);

对于键盘交互,我们在 tablist 上监听 keydown 事件。在此演示中,我们选择不激活 tab,当用户使用箭头键导航时,而是仅移动焦点。如果您想在 tab 获得焦点时显示它,您可以调用 showTab() 函数(稍后定义),而不是仅调用新标签的 focus()

js
tabList.addEventListener("keydown", (e) => {
  const currentTab = e.target;
  const currentIndex = tabs.indexOf(currentTab);
  if (currentIndex === -1) return; // Exit if the focused element is not a tab
  let newIndex = 0;

  switch (e.key) {
    case "ArrowRight":
      newIndex = (currentIndex + 1) % tabs.length;
      break;
    case "ArrowLeft":
      newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
      break;
    case "Home":
      newIndex = 0;
      break;
    case "End":
      newIndex = tabs.length - 1;
      break;
    default:
      return; // Exit if the key is not recognized
  }

  e.preventDefault();
  e.stopPropagation();
  tabs[newIndex].focus();
});

只有当用户在 tab 获得焦点时按下 EnterSpace,或者通过单击 tab 时,tab panel 才会激活。我们首先定义一个 showTab() 函数,该函数接受要显示的 tab 元素。

js
function showTab(targetTab) {
  // Unselect other tabs and set this tab as selected
  for (const tab of tabs) {
    if (tab === targetTab) continue;
    tab.setAttribute("aria-selected", false);
    tab.tabIndex = -1;
  }
  targetTab.setAttribute("aria-selected", true);
  targetTab.tabIndex = 0;

  // Hide other tab panels and show the selected panel
  const targetTabPanel = document.getElementById(
    targetTab.getAttribute("aria-controls"),
  );
  for (const panel of tabPanels) {
    if (panel === targetTabPanel) continue;
    panel.hidden = true;
  }
  targetTabPanel.hidden = false;
}

现在,我们可以在 click 事件或 keydown 事件上调用此函数。

js
tabs.forEach((tab) => {
  tab.addEventListener("click", (e) => {
    showTab(e.target);
  });
  tab.addEventListener("keydown", (e) => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      e.stopPropagation();
      showTab(e.target);
    }
  });
});

最佳实践

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

优先级顺序

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

规范

规范
无障碍富互联网应用程序 (WAI-ARIA)
# tab
未知规范

另见