Inclusive tabbed interfaces | Heydon Pickering
I've been digging into tabbed interfaces ("tabs") recently. As usual, Heydon's Inclusive Components has one of the best overviews and write-ups of the techniques used. I particularly love how Heydon breaks down ideas into logical progressive enhancement flows; in this case, that means thinking about a set of tabs as a linked table of contents followed by "sections" of content:
For my money, an embryonic tabbed interface is just a table of content with same-page links pointing at different sections of the page.
That allows a good baseline on which we can build a more "expected" visual interface for the tabs with CSS, and augment the functionality with JavaScript. The end result can be seen in this CodePen:
I have also cross-referenced with Adrian Roselli's older post (which now links to Heydon's early work on this subject) and see that the two are broadly similar: ARIA Tabs
The main takeaway here is that you should use a list of links as the tab controls, followed by a number of content sections. These are then bound together using a combination of ARIA roles, ARIA labels, and other assistive/semantic attributes (note that the inclusion of aria-controls
is not consistent across implementations and should be tested):
<ul role="tablist"> <li role="presentation"> <a id="tab1" href="#panel1" role="tab" aria-selected="true" aria-controls="panel1">Section 1</a> <a id="tab2" href="#panel2" role="tab" aria-selected="false" aria-controls="panel2">Section 2</a> </li> </ul> <section id="panel1" role="tabpanel" tabindex="-1" aria-labelledby="tab1"> <h2>Section 1</h2> ... </section> <section id="panel2" role="tabpanel" tabindex="-1" aria-labelledby="tab2" hidden> <h2>Section 2</h2> ... </section>
On using CSS to visually hide content, even without needing a visually "tabbed" interface:
What if I used some CSS to make just the chosen section from my table of contents visible? This is certainly possible using the :target
pseudo-class.
section:not(:target) { display: none; }
On how a basic table of contents may be the better approach:
I have encountered innumerable JavaScript-driven and ARIA-adorned, fully-fledged tabbed interfaces where simple tables of content atop page sections would have done just as well. Better even, since they are more robust and efficient. But for goodness' sake make them look like tables of content. Meet the expectations you set in visual design with your semantics and behaviors.
On why you shouldn't allow the tabs to be "tabbed" using the tab key, and that left/right arrow keys are a better alternative:
This problem is solved by delegating tab selection to arrow keys. The user is able to select and activate tabs using the arrow keys, while the Tab key is preserved for focusing contents within and below the active tab panel. To put it another way: Tab is not for tabs, which I concede is a bit confusing.
It's equally important that pressing Shift + Tab returns the user to the selected tab.
On ensuring non-visual users aren't forgotten. If we're altering tab order for keyboard users to be able to go from a tab selection directly to the content of that tab, then we should do the same for non-visual users as well by ensuring the common use of the down arrow is handled correctly:
Instead, we can intercept the down arrow key press and move focus programmatically to the open panel itself, making sure it isn't missed.
Although sighted keyboard users are less likely to use the down arrow key, it's important the focused tab panel has a focus style to indicate a change of focus location.
On dealing with tabbed interfaces on narrow viewports (responsive design):
A tabbed interface needs a breakpoint where there is insufficient room to lay out all the tabs horizontally. The quickest way to deal with this is to reconfigure the content into a single column.
On why switching to an accordion is probably not the best idea (though I'd argue that completely swapping out one component for another here would be a logical solution, and could provide a better UX, so long as it's well tested to ensure that the way the component is used is similar for all navigation methods):
Some have made noble attempts to reconfigure tabbed interfaces into accordion interfaces for small viewports. Given that accordions are structured, attributed, and operated completely differently to tabs, I would recommend against this.