6.6: Tab Panels
- Page ID
- 15624
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)Tab panels, much like accordions, are often used to conserve space and reduce scrolling. They are typically made up of a tablist that contains a series of tabs, each tab controlling the display of a panel. As each tab is activated, its associated panel is displayed and other panels are hidden. When a tab is selected, it is highlighted to indicate which tab and panel are active.
WAI-ARIA roles, states, and properties used in a tab panel
- role="tablist"
- role="tabpanel"
- role="tab"
- aria-hidden="[true|false]"
- tabindex = [0 | -1]
- aria-controls="[panel id]"
- aria-selected="[true|false]"
The following JSFiddle presents a typical tab panel widget. Review the JavaScript and HTML markup. Test the tab panel presented under the Result tab with ChromeVox to understand how it functions without any accessibility features added. You can work in JSFiddle itself by clicking “Edit in JSFiddle”, copying the accessibility/WAI-ARIA code described below to fix the accessibility of the tab panel before completing Activity 10 on the page that follows.
In our case, we are generating the tabs for each child <div>
defined in the HTML, though tabs and tab panels could be static HTML. The tablist is made up of a <ul>
and child <li>
elements. We assign role="tablist"
to the <ul>
to remove its list semantics and replace it with tab panel semantics.
Next, we add WAI-ARIA to the panels, assigning role="tabpanel"
to each of the original <div>
elements, hide them by default with aria-hidden="true"
, and finally adding tabindex="0"
to make the panels keyboard focusable.
The tabs themselves are now defined, replacing the list item semantics with tab semantics adding role="tab"
to each of the <li>
elements generated. We also need to define which tab controls which tabpanel, dynamically generating aria-controls="[panel_id]"
for each of the tabs.
When a tab is selected, we want to remove selection from other tabs with aria-selected="false"
, and remove keyboard access temporarily by assigning tabindex="-1"
to the unselected tabs, so that that tabpanel becomes next in the tab order, and users can navigate directly from the tab to the panel without having to pass through the other tabs in the tablist.
Likewise, when a tab is selected we assign aria-selected="true"
so screen readers announce the selected tab, we add tabindex="0"
as the roving tabindex to make that tab focusable.
As the tabs change, hide all the panels with aria-hidden="true"
so screen readers do not see them, then open the panel the current tab controls with aria-hidden="false"
so screen readers can see the active panel. These are added to the end of the selectTab()
function.
Adding Keyboard Operability
W3C describes authoring practices for tab panel keyboard interactions as follows.
Keyboard Interaction for Tab Panels
For the tab list:
- Tab: When focus moves into the tab list, places focus on the active
tab
element. When the tab list contains the focus, moves focus to the next element in the page tab sequence outside the tablist, which is typically either the first focusable element inside the tab panel or the tab panel itself. - When focus is on a tab element in a horizontal tab list:
- Left Arrow: moves focus to the previous tab. If focus is on the first tab, moves focus to the last tab. Optionally, activates the newly focused tab (See note below).
- Right Arrow: Moves focus to the next tab. If focus is on the last tab element, moves focus to the first tab. Optionally, activates the newly focused tab (See note below).
- When focus is on a tab in a tablist with either horizontal or vertical orientation:
- Space or Enter: Activates the tab if it was not activated automatically on focus.
- Home (Optional): Moves focus to the first tab
- End (Optional): Moves focus to the last tab.
- Shift + F10: If the tab has an associated pop-up menu, opens the menu.
- Delete (Optional): If deletion is allowed, deletes (closes) the current tab element and its associated tab panel. If any tabs remain, sets focus to the tab following the tab that was closed and activates the newly focused tab. Alternatively, or in addition, the delete function is available in a context menu.
Note:
- It is recommended that tabs activate automatically when they receive focus as long as their associated tab panels are displayed without noticeable latency. This typically requires tab panel content to be preloaded. Otherwise, automatic activation slows focus movement, which significantly hampers users’ ability to navigate efficiently across the tab list. For additional guidance, see 5.4 Deciding When to Make Selection Automatically Follow Focus.
- If the tabs in a tab list are arranged vertically:
- Down Arrow performs as Right Arrow is described above.
- Up Arrow performs as Left Arrow is described above.
- If the tab list is horizontal, it does not listen for Down Arrow or Up Arrow so those keys can provide their normal browser scrolling functions even when focus is inside the tab list.
As usual, the tab panel needs to be keyboard operable to be accessible to screen readers. The onKeyDown()
function is added to the functions, to add arrow key navigation between tabs, and between tabs and panels. Tab navigation and Enter keys are enabled by default and do not need to be defined here.
The onKeyDown
function is then added to each tab, referenced with jQuery’s .on('keydown')
function, added to the init()
function’s $tab
definition.
Now, with keyboard access and WAI-ARIA added to define the semantics of the tab panel, it should be fully functional for screen readers.
Accessible Tab Panel in Action
Watch the following video showing ChromeVox interacting with a tab panel. The Tab key is used to navigate into the tab panel and to the first tab. The arrow keys are used to move between tabs and, when on a tab, the Tab key is used to navigate to the associated panel. While on a panel, Shift + Tab is used to return to the tablist. There might also be Up and Down arrows enabled to move between tabs and panels, though we have not enabled them here. Aim to have the tab panel you update in the activity coming up on the next page operate and announce itself like the one in the video.
Video: Accessible Tab Panel