Many people never use a mouse. They include blind people using screen readers, people with motor disabilities using keyboards or switches, and power users who are simply faster with keys. If something only works with a mouse, it does not work for them.
This lesson covers the keyboard rules in WCAG, how to test them, and how to style focus well.
The rules
| Success criterion | Level | In short |
|---|---|---|
| 2.1.1 Keyboard | A | All functionality works from a keyboard, without needing specific timings |
| 2.1.2 No Keyboard Trap | A | Focus can always be moved away from any component with the keyboard |
| 2.4.3 Focus Order | A | Focus moves in an order that preserves meaning and operability |
| 2.4.7 Focus Visible | AA | The keyboard focus indicator is visible |
| 2.4.11 Focus Not Obscured (Minimum) | AA | The focused element is not entirely hidden by other content |
| 3.2.1 On Focus | A | Receiving focus does not trigger a change of context, such as opening a new page |
Test with the keyboard first
Keyboard testing needs no tools, and it finds serious problems fast. Put your mouse aside and use these keys:
| Key | Action |
|---|---|
Tab / Shift+Tab | Move to the next / previous focusable element |
Enter | Follow a link, press a button, submit a form |
Space | Press a button, tick a checkbox, scroll the page |
| Arrow keys | Move within radio groups, selects, tabs and menus |
Escape | Close a dialog, menu or popup |
Safari on macOS does not move focus to links with Tab by default. Turn on "Press Tab to highlight each item on a webpage" in Safari's Advanced settings, or use Option+Tab.
As you tab through, check:
- Can you reach every link, button and form control?
- Can you see where focus is at every step?
- Does the order follow the visual layout and reading order?
- Can you operate everything: menus, sliders, carousels, custom controls?
- Can you leave every component, including embedded videos and maps?
- Does focus ever disappear behind a sticky header or off-screen?
Native elements are keyboard accessible
Links with an href, buttons, and form controls are focusable and operable by default. Custom elements are not.
<!-- Not focusable, ignores Enter and Space -->
<div class="button" onclick="saveDraft()">Save draft</div>
<!-- Focusable, works with Enter and Space, announced as a button -->
<button type="button" onclick="saveDraft()">Save draft</button>
Recreating a button on a div needs tabindex, a role, key handlers for both Enter and Space, and disabled handling. Using button gives you all of that for free. Lesson 11 compares links and buttons in detail.
tabindex: three values that matter
| Value | Effect | When to use |
|---|---|---|
tabindex="0" | Adds the element to the tab order at its place in the source | Custom widgets that must receive focus, such as a scrollable region |
tabindex="-1" | Focusable by script, but not by Tab | Targets you move focus to with element.focus(), such as headings after navigation |
Positive values, such as tabindex="3" | Jumps the element ahead of everything else | Never. It breaks the natural order and is very hard to maintain |
<!-- A scrollable code sample that keyboard users can scroll with the arrow keys -->
<pre tabindex="0" aria-label="Example output"><code>...long output...</code></pre>
Focus order follows the DOM
Focus moves in source order. CSS that rearranges content visually, such as order in flexbox, grid-template-areas, flex-direction: row-reverse or absolute positioning, does not change the focus order. Users then see focus jump around unpredictably.
/* Visual order: Buy, Details. Focus order: Details, Buy. Confusing. */
.actions {
display: flex;
flex-direction: row-reverse;
}
Fix the HTML order instead of reordering with CSS. If a layout must change across breakpoints, check the tab order at each one.
Visible focus
Browsers draw a default focus ring, but many sites remove it with outline: none and never replace it. That fails 2.4.7.
Use :focus-visible, which matches when the browser decides a focus indicator should be shown: always for keyboard focus, and for text inputs even after a click. It avoids rings on mouse clicks on buttons, which was the usual reason people removed outlines.
:focus-visible {
outline: 3px solid #1a5fb4;
outline-offset: 2px;
}
/* On dark sections, switch the colour so it still contrasts */
.dark-band :focus-visible {
outline-color: #ffdd00;
}
Good focus indicators:
- Have at least 3:1 contrast against the surrounding background (1.4.11 Non-text Contrast).
- Are thick enough to see: 2 pixels or more. 2.4.13 Focus Appearance (AAA) sets detailed size and contrast targets worth aiming for.
- Use
outline, not onlybox-shadow. Windows contrast themes remove box shadows but keep outlines. - Do not rely on a colour change alone, such as a slightly darker background.
Never write *:focus { outline: none; } without a replacement. If you must hide the default ring for design reasons, provide a clearer one with :focus-visible in the same rule set.
Do not hide focus behind sticky content
2.4.11 Focus Not Obscured (Minimum) fails when a sticky header, cookie banner or chat widget completely covers the focused element. The scroll-padding property keeps scrolled-to elements clear of a fixed header.
html {
scroll-padding-top: 5rem; /* height of the sticky header plus a margin */
}
Avoid keyboard traps
A keyboard trap happens when focus enters a component and cannot leave, for example an embedded player that captures Tab. 2.1.2 requires that you can always move away. If a component needs non-standard keys to exit, tell users how.
Modal dialogs are a deliberate exception: focus should stay inside an open modal, but Escape and a close button must always let users out. Lesson 15 builds one.
Do not change context on focus
Under 3.2.1 On Focus, simply moving focus to an element must not submit a form, open a new window or move focus somewhere else. Act on a deliberate action such as a click or Enter, not on focus.
Summary
- Every function must work with the keyboard alone (2.1.1), with no traps (2.1.2).
- Test by tabbing through each page before using any tool.
- Native links, buttons and form controls are keyboard accessible; prefer them to custom elements.
- Use
tabindex="0"andtabindex="-1"only when needed, and never positive values. - Keep DOM order and visual order the same.
- Style
:focus-visiblewith a clear outline of at least 3:1 contrast, and keep focus clear of sticky content.