The keyboard

Make everything reachable and operable with a keyboard, in an order that makes sense.

06of 16
≈ 13Min read
6Parts

Who is using one

Screen reader users, people who cannot use a mouse, people using voice control or a switch, people whose trackpad has died — and every developer who has ever pressed Tab. If something can be clicked, it must be reachable and operable without a pointer. That is one WCAG criterion, and it is the one that fails most often.

The keys, and what owes what

The split matters: Tab moves between widgets, arrows move inside one. A group of radio buttons is one Tab stop, and the arrows choose. A set of tabs is one Tab stop. Getting that wrong means forty Tab presses to cross a toolbar.

KeyDoes
Tab / Shift+Tabmove between focusable things, in order
Enteractivate a link or a button; submit a form
Spaceactivate a button; check a checkbox; scroll the page
Arrowsmove within a widget: radio group, tabs, menu, select
Escapeclose a dialog, a menu, a popover — without saving
Home / Endfirst and last, inside a widget

The tab order is the DOM order

order, row-reverse, grid-area and absolute positioning can all reorder what you see without reordering what you Tab through. Keep the DOM in the reading order and use CSS for appearance, not for sequence.

and CSS can make it a lie
<div style="display: flex; flex-direction: row-reverse">
  <button>First in the DOM, last on the screen</button>
  <button>Second in the DOM, first on the screen</button>
</div>

<!-- Tab goes in DOM order. The eye goes in visual order. They must agree. -->

tabindex: three values, two of them fine

A positive tabindex puts an element before everything with tabindex 0 — including elements earlier on the page — and one on a page is enough to make the whole order unpredictable. tabindex="-1" is the useful one: it is how you move focus to a heading after a route change, or to the first error in a form.

and one to avoid entirely
<div tabindex="0">    <!-- add to the natural order, where it sits -->
<div tabindex="-1">   <!-- not in the order, but focusable from script: focus() -->
<div tabindex="5">    <!-- jump the queue. do not. -->
tabindex="0" does not make a div a button

It makes it focusable. It has no role, no name and no key handling. Either write all three, or — as the semantics lesson said — use a button.

Traps

A keyboard trap is anything you can Tab into and not out of: a third-party embed that swallows Tab, a custom editor, a modal that loops focus but has no Escape. It is a level-A failure because it ends the session — the person has to close the tab.

A modal dialog is the one place where trapping focus is correct, and it must come with Escape and a return of focus to whatever opened it. That is the Dialogs lesson.

Shortcuts

A single-letter shortcut steals that letter from voice-control users and from anyone typing in a field your handler did not exclude. If you offer them, require a modifier, or let them be remapped or turned off — which is a WCAG 2.1 criterion in itself.

Practice