Skip to Content
Component ExamplesTabs

Tabs

Accessible tab patterns — ARIA tab roles, keyboard navigation, and panel association

Maintained by Martin KrugerLast reviewed

Overview

Tabs organize content into selectable panels. The most common mistake is building tabs from <a> links or unsemantic <div> elements. These miss the required ARIA roles, states, and keyboard behavior that make tabs work for screen reader and keyboard users.

WCAG Criteria:

Key requirements:

  • The tab container needs role="tablist"
  • Each tab needs role="tab", aria-selected, and aria-controls pointing to its panel
  • Active tab: tabindex="0", inactive tabs: tabindex="-1"
  • Left/Right arrow keys move focus between tabs
  • Each panel needs role="tabpanel" and aria-labelledby pointing back to its tab
  • Only the active panel is visible

Tab Widget

ARIA Tab Widget vs. Link-Based Tabs

Inaccessible
Manage your account details and email address.
View inaccessible code
<!-- Links used as tabs — no role="tab", no aria-selected --> <div class="tab-list"> <a href="#" class="tab active" onclick="showTab(0)">Account</a> <a href="#" class="tab" onclick="showTab(1)">Security</a> <a href="#" class="tab" onclick="showTab(2)">Billing</a> </div> <div class="tab-panel active"> Manage your account details and email address. </div> <div class="tab-panel" style="display:none"> Update your password and configure two-factor authentication. </div> <div class="tab-panel" style="display:none"> View invoices and manage your payment methods. </div>
Accessible
Manage your account details and email address.
View accessible code
<div role="tablist" aria-label="Settings"> <button role="tab" id="tab-account" aria-selected="true" aria-controls="panel-account" tabindex="0"> Account </button> <button role="tab" id="tab-security" aria-selected="false" aria-controls="panel-security" tabindex="-1"> Security </button> <button role="tab" id="tab-billing" aria-selected="false" aria-controls="panel-billing" tabindex="-1"> Billing </button> </div> <div role="tabpanel" id="panel-account" aria-labelledby="tab-account"> Manage your account details and email address. </div> <div role="tabpanel" id="panel-security" aria-labelledby="tab-security" hidden> Update your password and configure two-factor authentication. </div> <div role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" hidden> View invoices and manage your payment methods. </div>

What’s wrong with link-based tabs?

  • <a> elements have role link, not tab — screen readers won’t identify the widget as a tab list
  • No aria-selected means the user can’t tell which tab is active
  • Arrow key navigation (the expected pattern for tab widgets) does not work
  • Screen reader users navigating by landmarks or roles won’t find a tab widget

Predicted semantic summary (exact output varies):

VersionAnnouncement on focus
Inaccessible (link)“Account, link” — no tab context
Accessible (tab)“Account, tab, 1 of 3, selected, Settings tab list”
Inactive tab”Security, tab, 2 of 3, Settings tab list”

Choose automatic activation only when panels appear without noticeable latency; otherwise use manual activation so Arrow keys move focus and Enter/Space selects. Support Left/Right for horizontal tabs, Up/Down for vertical tabs, Home/End, and—when tabs are removable—Delete plus a documented focus destination. Keep exactly one tab in the Tab sequence. For content that needs shareable URLs, browser history, or deep links, ordinary links and sections may be a better information architecture than an ARIA tab widget.


Resources