A pattern is a contract
The ARIA Authoring Practices describe each widget as three things: the roles in it, the states that must be kept true, and the keys it owes. Implement all three and assistive technology behaves as the user expects, because they have met the pattern elsewhere. Implement two of the three and you have something that announces itself as a tab list and cannot be used like one.
Disclosure — the one you need most often
Roles: a button. States: aria-expanded, kept true. Keys: Enter and Space — which a real button already gives you. That is the whole contract, and it is why a disclosure is the one custom widget it is safe to write.
<details>
<summary>Specification</summary>
<p>Oak, 890, ten days.</p>
</details>
<!-- or, when the design will not allow <details> -->
<button aria-expanded="false" aria-controls="spec">Specification</button>
<div id="spec" hidden>…</div>
Tabs
- Roles:
tablist,tab,tabpanel, and the list needs a name. - States: exactly one
aria-selected="true"; the other panelshidden. - Keys: ← and → move between tabs (Home and End to the ends). Tab moves out of the tab list into the panel.
- Roving tabindex: the selected tab has
tabindex="0", the rest-1. That is what makes the whole list one Tab stop.
<div role="tablist" aria-label="Door details">
<button role="tab" id="tab-spec" aria-selected="true" aria-controls="panel-spec" tabindex="0">Specification</button>
<button role="tab" id="tab-fit" aria-selected="false" aria-controls="panel-fit" tabindex="-1">Fitting</button>
</div>
<div role="tabpanel" id="panel-spec" aria-labelledby="tab-spec">…</div>
<div role="tabpanel" id="panel-fit" aria-labelledby="tab-fit" hidden>…</div>
Without it, a five-tab component is five Tab presses to get past — and the arrow keys do nothing, because the user never thinks to press them. The two halves only work together.
The others, briefly
If you are about to build one of these, read the Authoring Practices page for it and copy the key handling. And check first whether a <select>, a <datalist> or a plain list of links would do — they usually would.
| Widget | The hard part |
|---|---|
| Combobox | aria-expanded, aria-activedescendant, and never moving real focus into the list |
Menu (role="menu") | it is for application menus, not for navigation links — a <nav> with a disclosure is usually right |
| Tree | levels, expansion state and typeahead |
| Data grid | two-dimensional arrow navigation, and cells that can contain controls |
The test that matters
Unplug the mouse. Reach the widget with Tab, operate it with the keys its pattern specifies, and turn a screen reader on to hear what it announces at each step. Every widget in this lesson can be verified in about thirty seconds that way, and no automated tool will tell you what those thirty seconds will.