ARIA:选项卡角色
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
被选中或处于活动状态时,其他选项卡面板的 hidden
属性应设置为 true,直到用户选择与该选项卡面板关联的选项卡。当多个可选择 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
-
布尔值
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
后面的错觉。
<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
时更改活动tab
和tabpanel
。
为了完成第一个,我们监听tablist
上的keydown
事件。如果事件的key
是ArrowRight
或ArrowLeft
,我们将对此事件做出反应。我们首先将当前tab
元素的tabindex
设置为 -1,使其不再可制表。然后,如果按下右箭头,我们将选项卡焦点计数器增加 1。如果计数器大于我们拥有的tab
元素数量,我们将通过将该计数器设置为 0 来循环回到第一个选项卡。如果按下左箭头,我们将选项卡焦点计数器减少 1,如果它小于 0,我们将它设置为选项卡元素数量减 1(以到达最后一个元素)。最后,我们将focus
设置为索引等于选项卡焦点计数器的tab
元素,并将它的tabindex
设置为 0,使其可制表。
为了处理更改活动tab
和tabpanel
,我们有一个函数,它接收事件,获取触发事件的元素,触发元素的父元素及其祖父母元素。然后,我们找到父元素中所有具有aria-selected="true"
的选项卡,将其设置为false
,然后将触发元素的aria-selected
设置为true
。之后,我们在祖父母元素中找到所有tabpanel
元素,使它们全部hidden
,最后选择id
等于触发tab
的aria-controls
的元素,并删除hidden
属性,使其可见。
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 |
未知规范 |