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:
- 2.1.1 Keyboard — tabs must support arrow key navigation
- 4.1.2 Name, Role, Value — tab role and selected state must be exposed
Key requirements:
- The tab container needs
role="tablist" - Each tab needs
role="tab",aria-selected, andaria-controlspointing to its panel - Active tab:
tabindex="0", inactive tabs:tabindex="-1" - Left/Right arrow keys move focus between tabs
- Each panel needs
role="tabpanel"andaria-labelledbypointing back to its tab - Only the active panel is visible
Tab Widget
ARIA Tab Widget vs. Link-Based Tabs
Inaccessible
<!-- 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
<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>Live Preview
Manage your account details and email address.
Update your password and configure two-factor authentication.
View invoices and manage your payment methods.
What’s wrong with link-based tabs?
<a>elements have rolelink, nottab— screen readers won’t identify the widget as a tab list- No
aria-selectedmeans 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
What the screen reader announces:
| Version | Announcement 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” |