ARIA: tab 角色
ARIA 的 tab
角色表示 tablist
内的一个交互式元素,当该元素被激活时,它会显示其关联的 tabpanel
。
<button role="tab" aria-selected="true" aria-controls="tabpanel-id" id="tab-id">
Tab label
</button>
描述
具有 tab
角色的元素会控制具有 tabpanel
角色的关联元素的可视性。常见的用户体验模式是,视觉标签组位于内容区域的上方或侧面,选择不同的标签会更改内容,并使选定的标签比其他标签更突出。
具有 tab
角色的元素必须是 tablist
角色元素的子元素,或者其 id
是 tablist
的 aria-owns
属性的一部分。此组合向辅助技术表明该元素是一个相关元素组的一部分。一些辅助技术会提供 tablist
内 tab
角色元素的计数,并告知用户他们当前已定位到哪个 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
元素。
<div role="tab"><h3>Title of my tab</h3></div>
由于 tab
的后代是表现型的,因此以下代码等效
<div role="tab"><h3 role="presentation">Title of my tab</h3></div>
从辅助技术用户的角度来看,标题不存在,因为前面的代码片段在辅助功能树中等同于以下内容:
<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 内容。
<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
作为参数传递。
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()
。
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
获得焦点时按下 Enter 或 Space,或者通过单击 tab
时,tab panel 才会激活。我们首先定义一个 showTab()
函数,该函数接受要显示的 tab
元素。
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
事件上调用此函数。
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 |
未知规范 |