Tablist
A tablist is a group of tabs that are used to navigate between layered sections of content, known as tab panels. Each tab controls the visibility of a tab panel. Only one tab panel can be visible at a time. The tablist and its associated tab panels are referred to as a tabbed interface.
The tablist orientation defaults to horizontal, but could also be vertical. A horizontal tablist is usually placed directly above the currenty displayed tab panel, while a vertical tablist will be placed to its left.
Role
A group of tabs must be enclosed within a container element. The container element must have a role of tablist
.
1<!-- a tablist using a div container -->2<h2 id="tablist-heading">Orchestra instruments</h2>3<div role="tablist" aria-labelledby="tablist-heading">4 <!-- tabs go here -->5</div>67<!-- a tablist using a list container -->8<h2 id="tablist-heading">Orchestra instruments</h2>9<ul role="tablist" aria-labelledby="tablist-heading">10 <!-- tabs go here -->11</ul>
Accessibility label
The container element should have an accessibility label, though it is not required.
If the tablist has a visible label then an accessibility label should be provided using
the aria-labelledby
attribute that points to an element that contains the visible label text.
If there is not visible label then the aria-label
attribute should be used.
1<!-- a tablist with a visible label -->2<h2 id="tablist-heading">Orchestra instruments</h2>3<ul role="tablist" aria-labelledby="tablist-heading">4 <!-- tabs go here -->5</ul>67<!-- a tablist without a visible label -->8<ul role="tablist" aria-label="Orchestra instruments">9 <!-- tabs go here -->10</ul>
Tablist has tabs
The children of the container element must have a role of tab
. The tab
role must be applied to the element that represents the tab.
1<!-- a tablist using a div container -->2<h2 id="tablist-heading">Orchestra instruments</h2>3<div role="tablist" aria-labelledby="tablist-heading">4 <button role="tab" aria-selected="true" tabindex="0">Violin</button>5 <button role="tab" aria-selected="false" tabindex="-1">Viola</button>6 <button role="tab" aria-selected="false" tabindex="-1">Cello</button>7 <button role="tab" aria-selected="false" tabindex="-1">Bass</button>8</div>910<!-- a tablist using a list container -->11<h2 id="tablist-heading">Orchestra instruments</h2>12<ul role="tablist" aria-labelledby="tablist-heading">13 <li role="tab" aria-selected="true" tabindex="0">Violin</li>14 <li role="tab" aria-selected="false" tabindex="-1">Viola</li>15 <li role="tab" aria-selected="false" tabindex="-1">Cello</li>16 <li role="tab" aria-selected="false" tabindex="-1">Bass</li>17</ul>
Forbidden interactive children
Aside from the tabs, a tablist must not contain any interactive children such as links or buttons. Such children would be inaccessible to screen reader users since they will be excluded from the accessibility tree.
For clarity, this rule does not apply to tab panel elements since they are not children of the tablist container.
Tabs accessibility labels
Each tab in the tablist must have a unique accessibility label. Generally element that represents the tab (e.g. button
, div
, li
) will support
name from content, then its accessibility label could be given from a direct text node child, an image with an alternative text,
or even multiple children elements with an accessibility label. This ensures that the accessibility label is consistent with any visible label.
Using aria-label
or aria-labelledby
to specify the accessibility label is acceptable but may lead to inconsistencies with any visible label
so is not recommended.
1<!-- tabs labelled by visible text nodes -->2<h2 id="tablist-heading">Orchestra instruments</h2>3<div role="tablist" aria-labelledby="tablist-heading">4 <button role="tab" aria-selected="true" tabindex="0">Violin</button>5 <button role="tab" aria-selected="false" tabindex="-1">Viola</button>6 <button role="tab" aria-selected="false" tabindex="-1">Cello</button>7 <button role="tab" aria-selected="false" tabindex="-1">Bass</button>8</div>910<!-- tabs labelled by an image with alternative text -->11<h2 id="tablist-heading">Orchestra instruments</h2>12<div role="tablist" aria-labelledby="tablist-heading">13 <button role="tab" aria-selected="true" tabindex="0">14 <img src="violin.png" alt="Violin" />15 </button>16 <button role="tab" aria-selected="false" tabindex="-1">17 <img src="viola.png" alt="Viola" />18 </button>19 <button role="tab" aria-selected="false" tabindex="-1">20 <img src="cello.png" alt="Cello" />21 </button>22 <button role="tab" aria-selected="false" tabindex="-1">23 <img src="bass.png" alt="Bass" />24 </button>25</div>
Set size
Optionally, the aria-setsize
attribute may be used to indicate the number of tabs in the tablist. If the aria-setsize
attribute
is used, then it should be set to the number of tabs in the tablist. The aria-setsize
attribute must be set on the tabs
themselves, not the tablist container and it should be consistent across all tabs in the group.
Set position
Optionally, the aria-posinset
attribute may be used to indicate the position of a tab in the tablist. If the aria-posinset
attribute
is used, then it should be set to the position of the tab in the tablist.
ARIA owns
If the tablist should contain tabs that cannot be implemented as descendants of the tablist,
the aria-owns
attribute can be used to indicate that the tablist owns the tabs.
However, since the aria-owns
attribute is not supported by some assistive technologies,
it is best practice to avoid it. If it must be used, the aria-owns
attribute should be set
on the tablist and should point to the IDs of the owned tabs.
Any elements referenced by the aria-owns
attribute should have a role of tab
.
1<!-- tabs are not descendants of the tablist container -->2<h2 id="tablist-heading">Orchestra instruments</h2>3<div role="tablist" aria-labelledby="tablist-heading" aria-owns="violin viola cello bass" />4<div id="violin" role="tab" aria-selected="true" tabindex="0">Violin</div>5<div id="viola" role="tab" aria-selected="false" tabindex="-1">Viola</div>6<div id="cello" role="tab" aria-selected="false" tabindex="-1">Cello</div>7<div id="bass" role="tab" aria-selected="false" tabindex="-1">Bass</div>
Selected tabs
The aria-selected
attribute must be set on the tab that is selected. The aria-selected
attribute must be set to true
on the selected tab only.
The selected tab must be in the focus sequence, see Focus management.
Unreachable group
When semantic HTML controls such as button elements are used to implement tabs, it is possible to disable a tab by setting the disabled
attribute.
However, it is not acceptable for the selected tab to be disabled or not reacheable by keyboard navigation. This would make the entire tablist
unreachable by keyboard since the selected tab must receive focus when the tablist receives focus.
1✓ <button role="tab" aria-selected="true" tabindex="0">Violin</button>2<!-- not reachable by keyboard due to being disabled -->3✗ <button role="tab" aria-selected="true" tabindex="0" disabled>Violin</button>4<!-- not reachable by keyboard due to being excluded from the tab index -->5✗ <button role="tab" aria-selected="true" tabindex="-1">Violin</button>
Controlled tab panels
Each tab in the tablist should control the visibility of a tab panel. The relationship between tab and tab panel should be indicated using the
using the aria-controls
attribute on the tab, which should point to the ID of the controlled tab panel.
Tab panel roles
Tab panels must have a role of tabpanel
.
Tab panels accessibility labels
Tab panels must have an accessibility label. Since the name of the tab panel should be the same as the label of the tab that controls it,
the accessibility label of the tab panel should be provided by using the aria-labelledby
attribute that points to the ID of the tab
that controls it.
1<!-- a tablist using a div container -->2<h2 id="tablist-heading">Orchestra instruments</h2>3<div role="tablist" aria-labelledby="tablist-heading">4 <button id="violin" role="tab" aria-selected="true" tabindex="0">Violin</button>5 <button id="viola" role="tab" aria-selected="false" tabindex="-1">Viola</button>6 <button id="cello" role="tab" aria-selected="false" tabindex="-1">Cello</button>7 <button id="bass" role="tab" aria-selected="false" tabindex="-1">Bass</button>8</div>9<div id="violin-panel" role="tabpanel" aria-labelledby="violin" tabindex="0">...</div>10<div id="viola-panel" role="tabpanel" aria-labelledby="viola" hidden>...</div>11<div id="cello-panel" role="tabpanel" aria-labelledby="cello" hidden>...</div>12<div id="bass-panel" role="tabpanel" aria-labelledby="bass" hidden>...</div>
perceivable tab panels
The active tab panel must be visible and readable by screen readers i.e. aria-hidden="false"
(default).
1✓ <div id="violin-panel" role="tabpanel" aria-labelledby="violin" tabindex="0">...</div>2<!-- active tab panel not visible to screen reader -->3✗ <div id="violin-panel" role="tabpanel" aria-labelledby="violin" tabindex="0" aria-hidden="true">...</div>4<!-- active tab panel visible only to screen reader -->5✗ <div id="violin-panel" role="tabpanel" aria-labelledby="violin" tabindex="0" style="opacity: 0;">...</div>
Any inactive tab panel must be not visible and hidden from screen readers. Good techniques for making the
content invisible i.e. adding style attributes e.g display: none
or visibility: hidden
,
or using the hidden
attribute, will also hide the content from screen readers and ensure the tab
panel is inert and not focusable.
If other methods are used to make the tab panel invisible such as
setting the opacity to 0, then the tab panel must be explicity hidden from the sreen reader using
the aria-hidden="true"
attribute. The tab panel and its descendents must also be
excluded from the focus sequence either by setting the inert
attribute on the tab panel or by
setting the tabindex="-1"
on any potentially focsuable descendents.
1<!-- using the hidden attribute -->2✓ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" hidden>...</div>3<!-- using style attributes (can also be achieved by adding/removing CSS classes) -->4✓ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" style="display: none;">...</div>5✓ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" style="visibility: hidden;">...</div>6<!-- using aria-hidden="true" and inert -->7✓ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" style="opacity: 0;"8 aria-hidden="true" inert>...</div>9<!-- using aria-hidden="true" and tabindex="-1" -->10✓ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" style="opacity: 0;"11 aria-hidden="true">12 ...13 <button tabindex="-1">Click me</button>14 </div>15<!-- problematic examples -->16<!-- not using aria-hidden -->17✗ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" style="opacity: 0;">...</div>18<!-- not excluding elements from the focus sequence -->19✗ <div id="viola-panel" role="tabpanel" aria-labelledby="viola" style="opacity: 0;"20 aria-hidden="true">21 ...22 <button>Click me</button>23 </div>
Focus management
When focusing into a tablist, focus should be placed on the selected tab. If no tab is selected, then the focus should be placed on the
first tab. Once focus is in the group, the next Tab
key press should move focus to the active tab panel, which is controlled by the
selected tab. This is usually achieved by setting the tabindex="0"
on the active tab panel (recommended), or by having a focusable
element within the tab panel.
If focus is in the tablist and the Shift
key is held while pressing Tab
, focus should move to the previous focusable element before
the tablist.
There are two options for implementing focus management on the tabs:
- Make the selected tab focusable by adding the
tabindex="0"
attribute. Make the other tabs unfocusable by adding thetabindex="-1"
attribute.
1<h2 id="tablist-heading">Orchestra instruments</h2>2<div role="tablist" aria-labelledby="tablist-heading">3 <button role="tab" aria-selected="true" tabindex="0">Violin</button>4 <button role="tab" aria-selected="false" tabindex="-1">Viola</button>5 <button role="tab" aria-selected="false" tabindex="-1">Cello</button>6 <button role="tab" aria-selected="false" tabindex="-1">Bass</button>7</div>8<div id="violin-panel" role="tabpanel" aria-labelledby="violin" tabindex="0">...</div>9<div id="viola-panel" role="tabpanel" aria-labelledby="viola" hidden>...</div>10<div id="cello-panel" role="tabpanel" aria-labelledby="cello" hidden>...</div>11<div id="bass-panel" role="tabpanel" aria-labelledby="bass" hidden>...</div>
- Make the tablist container element focusable by adding the
tabindex="0"
attribute. Use thearia-activedescedant
attribute on the container with the ID of the selected tab. This will cause the browser to move focus to the selected tab when the tablist container is focused.
1<h2 id="tablist-heading">Orchestra instruments</h2>2<ul role="tablist" aria-labelledby="tablist-heading" tabindex="0" aria-activedescendant="violin">3 <li id="violin" role="tab" aria-selected="true">Violin</li>4 <li id="viola" role="tab" aria-selected="false">Viola</li>5 <li id="cello" role="tab" aria-selected="false">Cello</li>6 <li id="bass" role="tab" aria-selected="false">Bass</li>7</ul>8<div id="violin-panel" role="tabpanel" aria-labelledby="violin" tabindex="0">...</div>9<div id="viola-panel" role="tabpanel" aria-labelledby="viola" hidden>...</div>10<div id="cello-panel" role="tabpanel" aria-labelledby="cello" hidden>...</div>11<div id="bass-panel" role="tabpanel" aria-labelledby="bass" hidden>...</div>
Once focus is in the tablist, the Arrow
keys may be used to move focus and optionally selection between tabs. If selection does not move
automatically with focus, then the Space
or Enter
key should change the selection to the focused tab. If selection moves automatically
with focus then it should be consistent for all keyboard navigation operations.
Tablists may be horizontally or vertically oriented. The orientation of the tablist should be indicated using the aria-orientation
attribute,
see Keyboard navigation orientation.
In a horizontal tablist (default):
- The
ArrowRight
key should move focus and optionally change the selection to the next tab. If focus is on the last tab, it should loop around to the first tab. - The
ArrowLeft
key should move focus and optionally change the selection to the previous tab. If focus is on the first tab, it should loop around to the last tab. - It is best not to modify the behavior of the
ArrowUp
andArrowDown
so that their original functionality is maintained.
In a vertical tablist:
- The
ArrowDown
key should move focus and optionally change the selection to the next tab. If focus is on the last tab, it should loop around to the first tab. - The
ArrowUp
key should move focus and optionally change the selection to the previous tab. If focus is on the first tab, it should loop around to the last tab. - It is best not to modify the behavior of the
ArrowRight
andArrowLeft
so that their original functionality is maintained.
In both horizontal and vertical tablists extended keyboard support may be provided:
- The
Home
key may move focus and optionally change the selection to the first tab. - The
End
key may move focus and optionally change the selection to the last tab.
1const tablist = document.querySelector('[role="tablist"]');2const tabs = tablist.querySelectorAll('[role="tab"]');3const panels = document.querySelectorAll('[role="tabpanel"]');4const tablistOrientation = tablist.getAttribute("aria-orientation");56// using tabindex="0" on the active tab7function selectTab(selectedTab) {8 // deselect all tabs9 tabs.forEach(function (tab) {10 tab.setAttribute("aria-selected", "false");11 tab.setAttribute("tabindex", "-1");12 });1314 // set selected tab as selected15 selectedTab.setAttribute("aria-selected", "true");16 selectedTab.setAttribute("tabindex", "0");17 selectedTab.focus();1819 // hide all panels20 tabPanels.forEach(function (panel) {21 panel.setAttribute("hidden", "");22 panel.setAttribute("tabindex", "-1");23 });2425 // show selected panel26 var selectedPanel = document.getElementById(27 selectedTab.getAttribute("aria-controls")28 );29 selectedPanel.removeAttribute("hidden");30 selectedPanel.setAttribute("tabindex", "0");31}3233// using aria-activedescendant on the tablist34function selectTab(selectedTab) {35 // deselect all tabs36 tabs.forEach(function (tab) {37 tab.setAttribute("aria-selected", "false");38 });3940 // set selected tab as selected41 selectedTab.setAttribute("aria-selected", "true");42 tablist.setAttribute("aria-activedescendant", selectedTab.id);4344 // hide all panels45 tabPanels.forEach(function (panel) {46 panel.setAttribute("hidden", "");47 panel.setAttribute("tabindex", "-1");48 });4950 // show selected panel51 var selectedPanel = document.getElementById(52 selectedTab.getAttribute("aria-controls")53 );54 selectedPanel.removeAttribute("hidden");55 selectedPanel.setAttribute("tabindex", "0");56}5758// add click event listeners to tabs59tabs.forEach(function (tab) {60 tab.addEventListener("click", function (event) {61 selectTab(event.currentTarget);62 });63});6465// add keydown event listeners to tabs for keyboard navigation66tabs.forEach(function (tab) {67 tab.addEventListener("keydown", function (event) {68 // get the index of the current tab in the tablist69 var index = Array.prototype.indexOf.call(tabs, event.currentTarget);70 var length = tabs.length;7172 // check for arrow key keydowns73 if (74 (tablistOrientation === "horizontal" &&75 event.key === "ArrowRight") ||76 (tablistOrientation === "vertical" && event.key === "ArrowDown")77 ) {78 event.preventDefault();79 if (index === length - 1) {80 // move focus to the first tab81 selectTab(tabs[0]);82 } else {83 // move focus to the next tab84 selectTab(tabs[index + 1]);85 }86 } else if (87 (tablistOrientation === "horizontal" &&88 event.key === "ArrowLeft") ||89 (tablistOrientation === "vertical" && event.key === "ArrowUp")90 ) {91 event.preventDefault();92 if (index === 0) {93 // move focus to the last tab94 selectTab(tabs[length - 1]);95 } else {96 // move focus to the previous tab97 selectTab(tabs[index - 1]);98 }99 }100 });101});
Keyboard navigation orientation
When implementing a vertical tablist, the orientation of the tablist must be indicated using the aria-orientation="vertical"
attribute.
For horizontal tablists, the aria-orientation="horizontal"
attribute may be set but it is not required since horizontal is the default.
It is crucial that the keyboard navigation is consistent with the declared orientation of the tablist.