Keyboard Navigation: What Should Work on Your Website and How to Test It

Put the mouse down. Open your site and try to reach the "Buy" button or the contact form using only Tab. If focus disappears, gets stuck, or jumps back to the top of the page, you have just seen your site the way people with motor disabilities, screen reader users, and switch users see it. Automated checks won't show this, so the keyboard test has to be done by hand. It takes 15 minutes.

This post covers 12 checks I run on every audit. Each one lists the WCAG 2.2 criteria, what should happen, and what to change in the markup. I'll follow up with a video that walks through them on a live site.

Why it matters

Keyboard users are not only people who can't hold a mouse. They include screen reader, voice control, and switch users, people with wrist injuries, and anyone who simply works faster without a mouse. WCAG 2.1.1 requires that everything you can do with a mouse can also be done with a keyboard. For sites serving the EU market, this is part of the European Accessibility Act, in force since 28 June 2025.

Preparation

  • Test the published site, not a preview. Caches, scripts, and CDNs can change behavior.
  • Use 100% zoom and a normal window width, then repeat in a narrow window (the mobile menu behaves differently).
  • Turn on an on-screen key display: Carnac on Windows, KeyCastr on Mac. You'll want it for the video too.
  • In Safari on Mac, Tab skips some elements by default. Use Option+Tab or enable "All controls" in settings.
  • Keep a log: page, element, what's wrong, which criterion.

Keys to know

KeyWhat it does
Tab / Shift+TabNext / previous interactive element
EnterFollows a link, presses a button
SpacePresses a button, toggles a checkbox. On a link it only scrolls the page
← → ↑ ↓Choose within radio groups, lists, tabs, sliders
EscCloses menus, dialogs, suggestions
Alt+←Back in browser history

12 checks

1. The first Tab shows "Skip to content"

Open the page, click the address bar, and press Tab. The first stop should be a visible link that skips the header (WCAG 2.4.1). Enter on it should move focus to the content, not just scroll. The next Tab should land on the first element of the main content.

1<a class="skip-link" href="#main">Skip to content</a>
2…
3<main id="main" tabindex="-1">…</main>

Don't hide the link with display: none: it will never receive focus. Showing it only on focus is fine.

2. Focus is visible everywhere

Tab through the whole page. Every element needs a clear indicator (2.4.7) with at least 3:1 contrast against the background (1.4.11), and a sticky header or cookie banner must not cover it (2.4.11). If the styles contain outline: none, there must be a replacement.

Build the indicator with outline, not box-shadow: in Windows High Contrast mode the browser removes shadows and the focus ring vanishes.

3. Focus order matches reading order

Go through the page forward and backward with Shift+Tab. Focus should move the way the eye reads: top to bottom, left to right (2.4.3, 1.3.2). DOM order sets focus order. A positive tabindex and CSS order rearranging almost always break it.

A dropdown menu in the header is often built as a single element that both links to a section page and opens a submenu. That doesn't work from the keyboard. Use two elements: a link to the section and a button next to it.

1<a href="/desserts">Desserts</a>
2<button aria-expanded="false" aria-controls="desserts-menu">
3  Desserts: show categories
4</button>

A screen reader announces "collapsed" or "expanded" (4.1.2). Merely moving focus to an element must not open anything (3.2.1).

5. Esc closes and returns focus

Open a submenu, move inside it, and press Esc. The menu should close and focus should return to the button that opened it. If focus lands on body, the person loses their place and starts over from the top. With nested layers, Esc closes only the top one (2.1.2, 1.4.13).

6. Search with suggestions

Search with a suggestion dropdown follows the ARIA combobox pattern: role="combobox", aria-expanded, aria-controls pointing to the list, and aria-activedescendant for the highlighted option. Focus stays in the input so people can keep typing. A separate aria-live="polite" region announces the number of suggestions (4.1.2, 4.1.3).

7. Checkboxes, radio buttons, and star ratings

A real <input type="checkbox"> with a <label> toggles with Space, and clicking the whole label works. A radio group is a single Tab stop, and arrow keys change the choice. Native elements give you all of this without JavaScript, so build star ratings as radio buttons in a <fieldset> with a <legend> (1.3.1, 2.1.1).

8. Modal dialogs

Open a dialog. Focus should move inside right away, Tab should cycle within the dialog, the page underneath should be unreachable, Esc should close it, and focus should return to the element that opened it (2.1.2, 2.4.3). The simplest route is the native <dialog>:

1dialog.showModal(); // background inert, Esc closes, Tab stays inside

Focus briefly leaves for the browser UI and comes back. That is intended behavior, not a bug.

9. Drag-and-drop has a button alternative

Any list reordered by dragging needs a single-pointer alternative (2.5.7, new in WCAG 2.2). Buttons like "Move up: Broccoli" and "Move down: Broccoli" must name their item, or the list ends up with a dozen identical "Move up" buttons. After a move, focus stays on the button and a live region announces the new position.

10. Forms and errors

Submit the form empty. Focus should go to the first invalid field, the field gets aria-invalid="true", and the message names the field and the allowed range, such as "Age, years: 18–80", rather than just "Error" (3.3.1, 3.3.3). Group related fields in a <fieldset> and add autocomplete for name and email.

11. Scrollable regions

On a narrow screen, a wide table or chart inside an overflow container scrolls sideways by mouse or touch. If nothing inside can take focus, keyboard users can never reach the right-hand part (2.1.1, 1.4.10). The fix:

1<div role="region" aria-labelledby="table-title" tabindex="0" class="scroll">
2  <table>…</table>
3</div>

12. 400% zoom and high contrast

Press Ctrl and "+" up to 400%. At 1280px width this equals 320px: the page must not scroll sideways and the menu should collapse into a button (1.4.4, 1.4.10). Set font sizes in rem, not px. Then turn on a Windows contrast theme (left Alt + left Shift + Print Screen) and tab through again: focus must stay visible (1.4.11). Wrap animations in prefers-reduced-motion (2.3.3).

Checklist

  • The first Tab shows a skip link, and Enter moves focus to the content.
  • Focus is visible everywhere and built with outline.
  • Tab order matches reading order, with no positive tabindex.
  • Links navigate, buttons act, and no element does both.
  • Icon buttons have a name: "Remove: Potato", not "×".
  • Disclosure buttons have aria-expanded, toggles have aria-pressed.
  • Esc closes the top layer and focus returns to the opener.
  • Modal dialogs use <dialog>.
  • Action results are announced by a live region, but not on every keystroke.
  • A form error moves focus to the field, with aria-invalid and a clear message.
  • Every drag interaction has a button alternative.
  • A scrollable table sits in a named region with tabindex="0".
  • No sideways scrolling at 400% zoom.

What this test won't show

A keyboard test doesn't replace testing with a screen reader: NVDA or VoiceOver may announce roles and states differently than you expect. It also says nothing about your site in other languages. That's why I test the keyboard and the screen reader separately.

What's next

Try going through your own site with this list. If you find several issues and don't know where to start, you can order an audit on the Audit page and get a prioritized fix plan.

Let’s create together

or

0 / 500