Tabs
Hide and disclose panels of content via a series of interactive tabs.
Last updated
Hide and disclose panels of content via a series of interactive tabs.
Last updated
A tab is a control that allows the user to select and display a single panel of content from a group of choices. By decluttering the user-interface in this way, we say that tabs follow the principal of progressive disclosure.
Selecting a tab should update the visible panel without a full page reload. If a full page load is required instead (i.e. acting like a link), please see the fake tabs section below for more details.
Experience the tab pattern in action on our examples site.
Examine the required markup structure by viewing our bones site.
View a fully-style example on our eBay Skin site.
tabs: the composite patterns as a whole, containing a tablist, tabs and tabpanels
tabs heading: the heading that immediately precedes the tabs widget
tab list: contains two or more tabs
tab: a type of button that displays it's associated tabpanel
selected tab: the currently selected tab
tab panel: contains the content related to the tab
tab heading: the offscreen heading that maintains correct heading structure
autoSelect: for keyboard users, tab selection can either follow keyboard focus (known as auto selection), or require an additional ENTER
or SPACEBAR
press to set selection (known as manual selection).
Tab list must be preceded by a heading. All tabs must be thematically related to this heading. For example, a set of 'Shipping Services' tabs might contain a tab each for USPS, FedEx and UPS.
To maintain correct heading structure, tab panels should contain an offscreen heading. The level of this panel heading must be exactly one level lower than the heading preceding the tablist. The heading text must match the corresponding tab text.
Tab list must have exactly one selected tab.
If all tab panel content is rendered on page load, tabs should be configured with autoSelect
enabled.
If all tab panel content is rendered lazily on client (i.e. using AJAX call), tabs should be configured with autoSelect
turned off.
This section provides guidance for keyboard, screen reader and pointing devices.
Only one tab can be keyboard focusable at any time. This is known as a roving tab index.
For tabs with autoSelect
enabled, ARROW
keys move keyboard focus to next/previous tab and also select that tab (i.e. aria-selected="true"
).
For tabs without autoSelect
enabled, ARROW
keys move keyboard focus to next/previous tab, but ENTER
or SPACEBAR
key is required to set the tab to a selected state.
If tab panel contains focusable element(s), TAB
key on selected tab must move focus to first focusable element in tab panel.
If tab panel does not contain focusable element(s), TAB
key on selected tab must move focus to next focusable element on page.
Tab must be announce as "Tab".
Tab label must be announced, for example "Select Shipping for me".
Tab selected state must be announced.
Virtual cursor navigation can move from tab to tab without changing the active tab selection.
Our first example implementation will create a tabs widget with autoSelect
configuration enabled. All of the tab panel content will be rendered to the DOM on server side load.
The sample follows the Progressive Enhancement strategy; we build in a layered fashion that allows everyone to access the basic content and functionality of a web page.
The three layers are:
Content (HTML)
Presentation (CSS)
Behaviour (JS)
The tabs and their related content elements can be fully visible and accessible without CSS and JavaScript as simple hyperlinks and page anchors respectively.
For a tabs widget where content is not rendered on first server side load, using this progressive enhancement type approach is not as applicable.
The goal of our content layer is to add all of our tabs and their respective panel content to the page.
For the purposes of this example, all panel content will be rendered server-side. You may wish to consider lazy-loading the content of each panel with AJAX. If you do utilise lazy-loading, be aware that your content will not be available in a non-JavaScript scenario.
Links
The tabs begin life as simple same-page navigation links, linking to the content anchors (panels) below it on the same page:
We call this markup structure our bones; our CSS and JavaScript will be expecting this exact DOM structure convention.
This structure has been chosen carefully. It allows us to display tabs horizontally and vertically simply by changing the second class (to tabs--horizontal or tabs--vertical).
NOTE: we have found that in some browsers, activating a same page link will only scroll the browser to the target, but the focus is left behind on the link. Adding tabindex="-1" also helps move and set focus on the target element.
Checkpoint
That's it! Our content is available and accessible to anyone in a non-CSS and non-JS state.
The goal of our presentation layer is to style the links to look like folder style tabs.
How you choose to style the links is outside the scope of this document, because every website likes to make their tabs look slightly different!
Flash of Unstyled Content (FOUC)
FOUC may occur before JavaScript initialises the widget, i.e. all panel content may be visible briefly. One way to alleviate this is to set a fixed height on the tab panel container:
We have chosen an arbitrary value of 150px for our example. After our JavaScript initialises the widget, it's height will grow or shrink to match the content of the currently selected panel. Of course if fixed height is what you desire, then you can leave the fixed value in place.
Checkpoint
Our tabs now appear visually like tabs, and the panel content is still fully operable without JavaScript (albeit with ugly vertical scrollbars).
The goal of our JavaScript is to implement our interaction design.
Plugin Boilerplate
We start by caching references to our most important elements:
Our selectors are based on our bones markup convention.
ARIA Roles
How does a screen reader know this is a tabs widget? We must add ARIA roles to the tab list, tabs, and panels.
Remove Link Behaviour
We currently have links nested inside of our tab elements. To avoid conflicts with our tabs we must remove any semantics and behaviour, effectively turning them into span
tags.
ARIA States
How does a screen reader know which tab is currently selected and which panel is visible? We must add aria-selected
and hidden
states.
ARIA Properties
How does a screen reader know which panel belongs to which tab, and the label of each panel? We must add aria-controls
and aria-labelledby
properties.
Roving Tabindex
If there are many tabs it would require many TAB
key presses to navigate past the widget, therefore tabs should be navigated with ARROW
keys instead.
Only one tab can be focussable at any given time. This is always the "selected" tab. When a user tabs away from the widget and then back again, focus will return to this "selected" tab.
This behaviour is known as a roving tabindex. We provide a sample makeup-roving-tabindex module for you to reference.
State Management
When the roving tabindex changes, we must update the aria-selected
and hidden
states.
Prevent Page Scroll
When the selected tab has focus, we must prevent arrow keys and spacebar from scrolling the page. We provide another module, makeup-prevent-scroll-keys, to make this trivial.
Widget Init
Finally we can mark our widget as initialised. Now our CSS rules for our progressively enhanced widget will kick in.
Final Checkpoint
We have enhanced our markup with ARIA roles, states and properties for screen reader users, and implemented keyboard behaviour.
We have some experimental JavaScript modules that may assist you with creation of an accessible tabs widget:
makeup-roving-tabindex - Useful for implementing the arrow key behaviour to change tabs
makeup-prevent-scroll-keys - Useful for preventing keys from scrolling page while focus is on a widget