← Accessibility Insights & Resources

Keyboard Accessibility: A Practical Guide to WCAG Keyboard and Focus Requirements

What WCAG requires for keyboard users, how to test any page in five minutes, and the code patterns that fix the most common keyboard and focus failures.

Get a manual WCAG audit

Keyboard accessibility means everything on your website can be reached and used without a mouse. Someone pressing Tab should be able to move through every link, button and form field in a sensible order, always see where they are, activate controls with Enter or Space, and never get stuck. If a feature only works with a mouse or a touchscreen, it is not accessible.

The quickest way to find out where you stand is to unplug your mouse (or just stop touching it) and try to complete a key task on your site, such as booking, buying or sending a contact form. Most keyboard problems reveal themselves within a few minutes. This guide shows you what to look for, explains the WCAG success criteria behind each check, and gives you code to fix what you find.

Who depends on keyboard accessibility

Many people never use a mouse. People who are blind navigate with a screen reader driven by keyboard commands. People with motor disabilities may use a standard keyboard, a switch device, a mouth stick or other assistive technology that sends keystrokes to the browser. People with tremors may find a mouse too imprecise. Others are working around a temporary injury, or are simply faster on the keyboard. Get keyboard support right and you've covered a lot of assistive technology at once, because so much of it relies on the same keyboard interface.

The WCAG keyboard and focus requirements

Six success criteria do most of the work. All but one are in WCAG 2.0, which is the AODA's legal baseline for Ontario organizations; 2.4.11 is new in WCAG 2.2. See our explanation of the WCAG 2.2 success criteria for the wider picture.

Success criterionLevelWhat it requiresVersion
2.1.1 KeyboardAAll functionality works from a keyboard, without needing specific timingsWCAG 2.0
2.1.2 No Keyboard TrapAIf focus can move into a component, it can move out using the keyboard aloneWCAG 2.0
2.4.1 Bypass BlocksAA way to skip content repeated on every page, such as the navigationWCAG 2.0
2.4.3 Focus OrderAFocus moves in an order that preserves meaning and operabilityWCAG 2.0
2.4.7 Focus VisibleAAThe keyboard focus indicator is visibleWCAG 2.0
2.4.11 Focus Not Obscured (Minimum)AAA focused element is not entirely hidden by author-created contentWCAG 2.2

Two related criteria are worth knowing. Character Key Shortcuts (2.1.4, Level A, WCAG 2.1) requires that single-key shortcuts can be turned off, remapped or limited to when the relevant component has focus, because they can be triggered by accident, especially by speech input users. Focus Appearance (2.4.13) sets size and contrast rules for focus indicators, but it's Level AAA, so it's guidance rather than a typical requirement.

Keyboard navigation testing in five minutes

You don't need special software. Open the page in a desktop browser, click in the address bar, and use only these keys:

KeyWhat should happen
TabFocus moves to the next link, button or form control
Shift+TabFocus moves back to the previous one
EnterFollows a link, activates a button, submits a form
SpaceActivates a button, toggles a checkbox, opens a select
Arrow keysMove within a group: radio buttons, select options, tabs, sliders, menus
EscCloses a dialog, menu or popup and returns focus sensibly

Then work through this sequence:

  1. First Tab press. Does a "Skip to main content" link appear? Does it work?
  2. Tab through the whole page. Can you always see where focus is? Does it ever disappear behind a sticky header, cookie banner or chat widget?
  3. Watch the order. Does focus follow the visual reading order, or jump around the page?
  4. Open every menu, accordion, tab set and dropdown. Do they open with Enter or Space, and can you reach everything inside them?
  5. Open a dialog or popup. Does focus move into it? Does Esc close it? Does focus return to the button you started from?
  6. Complete a form, including any date picker, file upload or custom select, and submit it.
  7. Check for traps. Tab into embedded maps, video players and third-party widgets, and make sure you can Tab back out.

Testing on a Mac with Safari? By default, Safari's Tab key skips links. Turn on the option in Safari's Advanced settings that lets Tab highlight each item on a webpage, or test in Chrome or Firefox, before deciding your links are unreachable.

Native elements vs div-buttons

The single biggest source of keyboard failures is a clickable element that isn't a real control. A <div> or <span> with a click handler can't be reached with Tab, doesn't respond to Enter or Space, and isn't announced as a button by screen readers.

<!-- Before: mouse only -->
<div class="btn" onclick="saveDraft()">Save draft</div>

<!-- After: focusable, works with Enter and Space, announced as a button -->
<button type="button" class="btn" onclick="saveDraft()">Save draft</button>

A native <button> gives you focus, keyboard activation and the correct role for free, and you can style it to look like anything. You can retrofit a div with role="button", tabindex="0" and key handlers, but you'll be rebuilding what the browser already provides, and it's easy to miss a detail. Our guide to ARIA best practices covers why native HTML should come first.

Use the right element for the job: a link (<a href>) takes you somewhere; a button does something on the current page. A link without an href isn't focusable, so "links" that only run JavaScript are a common failure too.

Focus indicator WCAG requirements and :focus-visible

Success criterion 2.4.7 requires a visible focus indicator. The most common way sites fail it is a single line of CSS, often added to remove the outline that appears when someone clicks a button: :focus { outline: none; }. That removes it for keyboard users too.

The :focus-visible pseudo-class solves the original complaint. Browsers apply it when focus should be shown, such as keyboard navigation, and generally not after a mouse click on a button. Style it clearly instead of removing focus styles:

/* A clear, two-tone focus ring that shows on light and dark backgrounds */
:focus-visible {
  outline: 3px solid #1a56db;
  outline-offset: 2px;
  box-shadow: 0 0 0 2px #ffffff;
}

/* Only remove the default ring where :focus-visible provides one */
:focus:not(:focus-visible) {
  outline: none;
}

Check the ring's contrast. A custom indicator falls under Non-text Contrast (1.4.11) and needs 3:1 against adjacent colours. The blue above is 6.18:1 on white but only 2.38:1 on a dark slate background, which is why the white inner ring is there. Our colour contrast accessibility guide explains how to measure it.

Focus Not Obscured: sticky headers and banners (2.4.11)

WCAG 2.2 added a requirement that a focused element must not be entirely hidden by content you added, such as a sticky header, sticky footer, cookie banner or chat launcher. Partly covered passes at AA, though it's better avoided. The usual fix for sticky headers is CSS scroll padding, so the browser scrolls focused elements clear of the header:

html {
  scroll-padding-top: 5rem; /* at least the height of the sticky header */
}

For a cookie banner, either make it a modal dialog that takes focus until the visitor chooses, or make sure it doesn't sit on top of focusable content.

Focus order and tabindex rules

Focus order (2.4.3) follows the order of elements in the HTML. Problems appear when the visual layout and the source order disagree, for example when CSS order, flex-direction: row-reverse or grid placement moves things around visually but not in the DOM. Fix the source order rather than patching it with tabindex.

  • tabindex="0" puts an element into the natural tab order. Use it only on custom interactive widgets that genuinely need focus.
  • tabindex="-1" makes an element focusable by script but not by Tab. It's useful for moving focus to a heading, an error summary or a dialog container.
  • Never use a positive tabindex (tabindex="1" and up). It pulls elements ahead of everything else on the page, creating an order that matches nothing on screen, and it breaks the moment anyone adds new content.
  • Don't add tabindex="0" to headings, paragraphs or other static text. It adds tab stops that do nothing, and screen reader users already have their own ways to read text.

Skip links (2.4.1 Bypass Blocks)

Without a skip link, a keyboard user has to Tab through your entire header and navigation on every page before reaching the content. A skip link is the first focusable element on the page and jumps to the main content. It can be visually hidden until it receives focus:

<a class="skip-link" href="#main">Skip to main content</a>
<header>...</header>
<main id="main">...</main>

.skip-link {
  position: absolute;
  left: 1rem;
  top: -10rem;
}
.skip-link:focus {
  top: 1rem;
}

Proper landmarks (<header>, <nav>, <main>) and headings also help screen reader users bypass blocks, but they don't help sighted keyboard users, so a skip link is still worth having.

Want every keyboard path on your site tested?

Our manual audits test real tasks with a keyboard and screen readers, and tell you the fix for each barrier, not just where it is.

See our WCAG audit

Keyboard traps (2.1.2)

A keyboard trap is any place where focus goes in and can't come out. They're most common in embedded content: third-party video players, maps, rich text editors and some chat widgets. If a component needs something other than Tab, Shift+Tab or the arrow keys to exit, the criterion requires that you tell users how.

A modal dialog that keeps focus inside it while it's open is not a trap, as long as the user can close it with Esc or a visible close button. That containment is intentional and expected.

Modals and focus management

Dialogs are where focus management matters most. When a dialog opens, focus should move into it. While it's open, Tab shouldn't wander into the page behind it. Esc should close it, and focus should return to the control that opened it. The native <dialog> element opened with showModal() handles much of this: it moves focus into the dialog, makes the rest of the page inert and closes on Esc.

<button type="button" id="open-filters">Filters</button>

<dialog id="filters" aria-labelledby="filters-title">
  <h2 id="filters-title">Filter results</h2>
  ...
  <button type="button" id="close-filters">Close</button>
</dialog>

<script>
  const dialog = document.getElementById('filters');
  document.getElementById('open-filters')
    .addEventListener('click', () => dialog.showModal());
  document.getElementById('close-filters')
    .addEventListener('click', () => dialog.close());
</script>

Test that focus lands back on the "Filters" button after closing. If you build a custom modal without <dialog>, you'll need to do all of this yourself, usually with the inert attribute on the background content plus a script that moves and restores focus.

Other common keyboard failures

  • Hover-only menus. Dropdown navigation that opens on mouse hover but not on focus or Enter.
  • Custom selects and date pickers that ignore arrow keys or can't be closed.
  • Carousels with arrows that aren't buttons, or that hide slides that can still receive focus.
  • Drag and drop with no keyboard alternative, such as reordering a list.
  • Content revealed on focus that disappears before it can be used, or tooltips that can't be dismissed with Esc.
  • Hidden content still in the tab order, such as closed off-canvas menus, so focus vanishes into elements nobody can see.

An automated scan can flag some of these, like positive tabindex values or controls without names, but it can't press keys and judge what happens. Keyboard testing is manual work. If you need to check assistive technology too, our screen reader testing guide picks up where this one ends.

Keyboard accessibility checklist

  • Every link, button and form control can be reached with Tab and Shift+Tab.
  • Everything works with Enter, Space and arrow keys as expected.
  • Focus is always visible, with 3:1 contrast for custom indicators.
  • Focused elements are never completely hidden by sticky or floating content.
  • Focus order matches the visual reading order.
  • No positive tabindex values anywhere.
  • A working skip link is the first focusable element.
  • No keyboard traps, including in embedded third-party content.
  • Dialogs take focus, close with Esc and return focus to their trigger.
  • Hidden content is removed from the tab order.

For the formal wording and examples, see W3C's Understanding 2.1.1 Keyboard and Understanding 2.4.11 Focus Not Obscured (Minimum).

Frequently asked questions

What is keyboard accessibility?

It means every function of a website can be used with a keyboard alone: reaching each control, seeing where focus is, operating controls and leaving any component, all without a mouse.

How do I test keyboard navigation on my website?

Put the mouse aside and use Tab, Shift+Tab, Enter, Space, the arrow keys and Esc to complete your site's key tasks. Watch for missing focus indicators, illogical order, unreachable controls and places you can't leave.

Is removing the focus outline a WCAG failure?

Removing it without a visible replacement fails 2.4.7 Focus Visible. Style :focus-visible instead so keyboard users always see a clear indicator.

Is a modal dialog a keyboard trap?

Not if users can close it with the keyboard, for example with Esc or a close button. Keeping focus inside an open modal is expected behaviour, not a trap.

Should I ever use a positive tabindex?

No. Use tabindex="0" for custom widgets and tabindex="-1" for elements you focus by script. Fix focus order by fixing the HTML source order.

Does the AODA require WCAG 2.2 Focus Not Obscured?

No. The AODA's legal baseline is WCAG 2.0 Level AA, and 2.4.11 was added in WCAG 2.2. It's still good practice, and contracts and procurement documents increasingly ask for WCAG 2.1 or 2.2.