Molecules
Tabs
A row of Tabs for moving between sections of a site or switching the view of one panel.
Water weekly in summer and keep out of full sun.
Plant in autumn, 40cm apart.
Code
<div class="tabbed-content">
<div class="tab-list" role="tablist" aria-label="Plant details">
<button type="button" role="tab" id="default-tab-1" class="tab" aria-selected="true" aria-controls="default-panel-1">
<span class="tab__text">Care</span>
</button>
<button type="button" role="tab" id="default-tab-2" class="tab" aria-selected="false" aria-controls="default-panel-2" tabindex="-1">
<span class="tab__text">Planting</span>
</button>
</div>
<div id="default-panel-1" role="tabpanel" class="tab-panel" aria-labelledby="default-tab-1" tabindex="0">
<p>Water weekly in summer and keep out of full sun.</p>
</div>
<div id="default-panel-2" role="tabpanel" class="tab-panel" aria-labelledby="default-tab-2" tabindex="0" hidden>
<p>Plant in autumn, 40cm apart.</p>
</div>
</div>
Why it exists
Tabs show one thing at a time and keep the others a click away. They cover two jobs that look alike and behave differently. Moving between pages is a list of links in a <nav>, which the browser already handles. Switching panels on one page is role="tablist", which promises arrow keys, and the library ships no script for them. Pick by which job you have, since the look won’t tell you.
Selection lives in aria-selected or aria-current, with no class. What a screen reader announces and what the eye sees come from the same attribute, so they can’t disagree.
Every axis sits on the list, because Tabs in a row should match each other. The list draws one indicator and slides it to the selected Tab, so a change of Tab reads as one movement.
When to use
.tab-listof links for navigation: site sections, a sub-nav or a floating dock, where each Tab is a page..tabbed-contentfor panels of one page, when people need only one at a time.- Not for steps in order: that is a Stepper.
- Not for reading several sections together: an Accordion keeps them open at once, and plain headings let people scan.
- Not for an action: something that changes data is a Button.
Rules
- Selected state is ARIA only:
aria-selected="true"in a tablist,aria-current(any value but"false") in navigation. No class. - Underline is for navigation and sections; segmented switches a view.
- Icon-only Tabs:
.tab--icon, with the label kept in.tab__text..hide-labelson the list, or--ui-labels: hiddenon a container, collapses labels to icons. - Counts: hide a Badge from assistive technology and spell the count out in visually hidden text.
Keyboard and screen readers
Navigation lists of links need none of this. The browser gives each link its place in the tab order, Enter follows it, and aria-current is announced.
For role="tablist", the product supplies the WAI-ARIA Tabs pattern. The examples on this page run a demo of it:
- Tab enters the list on the selected Tab only (roving
tabindex), then moves on to its panel. Each panel hastabindex="0". - Left and Right arrows move between Tabs, or Up and Down when vertical.
- Home and End go to the first and last Tab.
- A screen reader announces each as a Tab, selected or not, from its role and
aria-selected.
Navigation
Code
<nav class="tab-list" aria-label="Repository sections">
<a href="/repo/code" class="tab" aria-current="page"><span class="tab__text">Code</span></a>
<a href="/repo/issues" class="tab">
<span class="tab__text">Issues</span>
<span class="badge badge--sm" aria-hidden="true"><span class="badge__text">14</span></span>
<span class="sr-only">, 14 open</span>
</a>
<a href="/repo/pulls" class="tab"><span class="tab__text">Pull requests</span></a>
</nav>
<!-- As a list: <ul> and <li> take display: contents -->
<nav class="tab-list tab-list--plain" aria-label="Site">
<ul>
<li><a href="/docs" class="tab" aria-current="page"><span class="tab__text">Docs</span></a></li>
<li><a href="/components" class="tab"><span class="tab__text">Components</span></a></li>
</ul>
</nav>
Tabbed Content
Your name, email address and plan.
Your password and signed-in devices.
Your photo and a short bio.
Which emails you receive.
Code
<div class="tabbed-content">
<div class="tab-list" role="tablist" aria-label="Account settings">
<button type="button" role="tab" id="tab-1" class="tab" aria-selected="true" aria-controls="panel-1">
<span class="tab__text">Overview</span>
</button>
<button type="button" role="tab" id="tab-2" class="tab" aria-selected="false" aria-controls="panel-2" tabindex="-1">
<span class="tab__text">Security</span>
</button>
</div>
<div id="panel-1" role="tabpanel" class="tab-panel" aria-labelledby="tab-1" tabindex="0">
<p>Your name, email address and plan.</p>
</div>
<div id="panel-2" role="tabpanel" class="tab-panel" aria-labelledby="tab-2" tabindex="0" hidden>
<p>Your password and signed-in devices.</p>
</div>
</div>
<!-- Beside the panels: the list becomes vertical -->
<div class="tabbed-content tabbed-content--vertical">
<div class="tab-list" role="tablist" aria-label="Profile settings">
<button type="button" role="tab" id="side-tab-1" class="tab" aria-selected="true" aria-controls="side-panel-1">
<span class="tab__text">Profile</span>
</button>
<button type="button" role="tab" id="side-tab-2" class="tab" aria-selected="false" aria-controls="side-panel-2" tabindex="-1">
<span class="tab__text">Notifications</span>
</button>
</div>
<div id="side-panel-1" role="tabpanel" class="tab-panel" aria-labelledby="side-tab-1" tabindex="0">
<p>Your photo and a short bio.</p>
</div>
<div id="side-panel-2" role="tabpanel" class="tab-panel" aria-labelledby="side-tab-2" tabindex="0" hidden>
<p>Which emails you receive.</p>
</div>
</div>
Segmented
Code
<div class="tab-list tab-list--segmented tab-list--subtle tab-list--round" role="tablist" aria-label="Billing period">
<button type="button" role="tab" class="tab" aria-selected="true">
<span class="tab__text">Monthly</span>
</button>
<button type="button" role="tab" class="tab" aria-selected="false" tabindex="-1">
<span class="tab__text">Annual</span>
<span class="status status--accent status--sm"><span class="status__text">Save 20%</span></span>
</button>
</div>
<!-- Icon-only -->
<div class="tab-list tab-list--segmented tab-list--round" role="tablist" aria-label="Editor view">
<button type="button" role="tab" class="tab tab--icon" aria-selected="true">
<svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Code"/></svg>
<span class="tab__text">Code</span>
</button>
<button type="button" role="tab" class="tab tab--icon" aria-selected="false" tabindex="-1">
<svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Edit"/></svg>
<span class="tab__text">Design</span>
</button>
</div>
Do and Don’t
| Do | Don’t |
|---|---|
Mark the selected tab with aria-selected or aria-current. | Add a selected class. |
| Set axes on the list, or a flag on a container. | Put style or size modifiers on a Tab. |
Supply roving tabindex and arrow keys for role="tablist". | Ship a tablist with no keyboard script. |
Use links in a <nav> when each Tab is a page. | Use role="tablist" for site navigation. |
Keep the label in .tab__text on an icon-only tab. | Leave an icon tab with no name. |
| Hide a count Badge and spell the count out in hidden text. | Leave a bare number to be read out. |
In Storybook
- Tab variants and states
- Tab List variants and states
- Tabbed Content variants and states
- Tab tokens
- Tab List tokens
- Tabbed Content tokens
See also
- Button: An action on the page, such as saving a Form, opening a Dialog or changing a setting.
- Badge, Status, Chip and Kbd: Small labels chosen by their job, such as a count on something, the state of something, a value someone chose or a key to press.
- Sidebar: A side panel beside the main content, for section navigation, filters or tools.
- Card: A surface with named parts, such as a header, a body and a footer, for one thing a person can read, compare or act on.