Custom widgets

Build the two custom widgets you will actually need, with the roles, states and keys their patterns require.

10of 16
≈ 14Min read
5Parts

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.

and HTML already has it
<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 panels hidden.
  • 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.
the roles and the relationships
<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>
Roving tabindex is the part people skip

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.

WidgetThe hard part
Comboboxaria-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
Treelevels, expansion state and typeahead
Data gridtwo-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.

Practice