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
未知規範

另請參見