Skip to content
Accessibility Helper

Keyboard accessibility: how to test and fix your site

A 5-minute keyboard test for any website, the WCAG criteria behind it, and code fixes for focus styles, skip links, tab order and modals.

A website is keyboard accessible when everything you can do with a mouse also works with a keyboard alone, and you can always see where you are on the page. You can test this yourself in about five minutes with the Tab, Shift+Tab, Enter, Space, arrow and Esc keys. Most fixes come down to using native HTML elements and giving focus a clear, visible style.

Who relies on a keyboard?

More people than you might think. W3C lists people who are blind and can’t use a mouse, which needs eye-hand coordination, and people with low vision who find it hard to track a pointer. Some people with hand tremors find a mouse very difficult. Others use alternate keyboards or software that acts like a keyboard, such as speech input, sip-and-puff software and on-screen keyboards.

If your site works with a keyboard, it works with all of these.

Which WCAG criteria cover keyboard access?

Success criterion Level What it means
2.1.1 Keyboard A Everything works through a keyboard, without needing specific timing for keystrokes.
2.1.2 No Keyboard Trap A If focus can move into something, it can move out again using the keyboard.
2.4.1 Bypass Blocks A There’s a way to skip content repeated on every page, such as the header and menu.
2.4.3 Focus Order A Focus moves in an order that preserves meaning and operability.
2.4.7 Focus Visible AA You can see which element has keyboard focus.
2.4.11 Focus Not Obscured (Minimum) AA The focused element isn’t entirely hidden by content you added, such as a sticky header. New in WCAG 2.2.
2.4.13 Focus Appearance AAA The focus indicator is at least as large as a 2 CSS pixel thick outline around the element and has 3:1 contrast between focused and unfocused states. New in WCAG 2.2.

The 2.1.1 criterion has one narrow exception: functions that depend on the path of your movement, such as freehand drawing, rather than just the start and end points. WCAG 2.2 also added a stricter AAA version of 2.4.11, 2.4.12 Focus Not Obscured (Enhanced), where no part of the focused element may be hidden.

How do I test my site with a keyboard in 5 minutes?

Put your mouse or trackpad out of reach first. It’s surprisingly hard not to use it.

These are the keys you’ll need:

Key What it should do
Tab Move to the next link, button or form field
Shift+Tab Move back to the previous one
Enter Follow a link or press a button
Space Press a button, tick a checkbox, scroll the page
Arrow keys Move between radio buttons, menu items, tabs or slider values
Esc Close a menu, popup or dialog

Then work through your home page, a product or content page, and a form such as checkout or contact:

  1. Load the page and press Tab once. If you have a skip link, it should appear now. Press Enter and check that the next Tab press lands in the main content.
  2. Keep pressing Tab. Can you see where focus is every time? Is it ever hidden behind a sticky header, chat button or cookie banner?
  3. Watch the order. Does focus move through the page in the order you’d read it? Does it jump around or land on hidden items?
  4. Use everything. Open the menu, change a quantity, pick a variant, submit the form. Each control should work with Enter, Space or the arrow keys.
  5. Open every popup. For menus, dialogs and cart drawers, focus should move into them, Esc should close them, and focus should then return to the button you used to open them.
  6. Go backward. Press Shift+Tab through the same areas. Check that you never get stuck anywhere.

Write down each problem and where it happened. That list is your fix plan.

How do I fix common keyboard problems?

The most common cause of keyboard problems is a div or span made clickable with JavaScript. It looks like a button, but it can’t be focused, Enter and Space do nothing, and screen readers don’t announce it as a button.

<!-- Can't be reached or used with a keyboard -->
<div class="add-to-cart" onclick="addToCart()">Add to cart</div>

<!-- Focusable, works with Enter and Space, announced as a button -->
<button type="button" class="add-to-cart" onclick="addToCart()">
  Add to cart
</button>

A simple rule: if it goes somewhere, use a link (an a element with an href). If it does something on the page, use a button. Adding tabindex="0" to a div makes it focusable, but it still has no button role and no Enter or Space behavior, so the native element is the better choice.

Make focus clearly visible

Browsers draw a focus indicator by default, but many sites remove it. Removing it without a replacement is a documented failure of 2.4.7 Focus Visible (F78).

/* Don't do this */
:focus {
  outline: none;
}

Instead, give focus a strong style of your own with :focus-visible. Browsers apply it when someone navigates with the keyboard and usually not after a mouse click, so you don’t have to choose between keyboard users and a clean look for mouse users.

:focus-visible {
  outline: 3px solid #2453d6;
  outline-offset: 3px;
  border-radius: 4px;
}

Check the indicator’s contrast as well. Under 1.4.11 Non-text Contrast, the focus indicator needs enough contrast against the background next to it. A solid outline of at least 2 CSS pixels, offset from the element, is also a good start toward 2.4.13 Focus Appearance.

Keep the tab order logical

Tab order follows the order of elements in your HTML. If the visual order and the source order match, focus order usually takes care of itself.

Avoid positive tabindex values like tabindex="3". They pull elements ahead of everything else and quickly make the order confusing. MDN recommends using only 0 and -1:

  • tabindex="0" adds an element to the normal tab order, in source order.
  • tabindex="-1" lets you move focus to an element with JavaScript without adding it to the tab order, which is useful for a dialog or an error summary.

Be careful with CSS that reorders content visually, such as the flexbox order property. It changes what people see, not the tab order.

A skip link is the first focusable item on the page and jumps straight to the main content, so keyboard users don’t have to tab through the whole header on every page. It’s one of the ways to meet 2.4.1 Bypass Blocks (G1). W3C says it’s preferable for the link to be visible at all times, but a link that appears when it receives focus also meets the criterion.

<body>
  <a class="skip-link" href="#main">Skip to main content</a>
  <header>…</header>
  <main id="main">…</main>
</body>
.skip-link {
  position: absolute;
  left: 1rem;
  top: -100px;
  padding: 0.75rem 1rem;
  background: #ffffff;
  color: #0f1b2d;
}

.skip-link:focus {
  top: 1rem;
}

Stop sticky headers from hiding focus

2.4.11 Focus Not Obscured (Minimum) fails when a focused element is entirely hidden behind content you added. Sticky headers, sticky footers and cookie banners are the usual causes. When someone tabs to a link just under a sticky header, the browser scrolls it into view, right underneath the header.

The CSS scroll-padding properties tell the browser to leave room (C43):

:root {
  --header-height: 4.5rem;
}

html {
  scroll-padding-top: calc(var(--header-height) + 1rem);
}

Use scroll-padding-bottom the same way for a sticky footer or a banner fixed to the bottom of the screen. For cookie banners, W3C also describes making the banner modal, so people deal with it before moving through the page.

Manage focus in dialogs and menus

When a modal dialog opens, focus should move into it. Tab and Shift+Tab should stay inside it, Esc should close it, and focus should return to the button that opened it. Those are the expectations in W3C’s modal dialog pattern.

The native dialog element opened with showModal() does most of this for you. MDN notes that it makes the rest of the page inert, closes on Esc, and moves focus to the first focusable element inside, or to the element with autofocus.

<button type="button" id="open-size-guide">Size guide</button>

<dialog id="size-guide" aria-labelledby="size-guide-title">
  <h2 id="size-guide-title">Size guide</h2>
  <p>…</p>
  <button type="button" id="close-size-guide" autofocus>Close</button>
</dialog>
const opener = document.getElementById("open-size-guide");
const dialog = document.getElementById("size-guide");
const closer = document.getElementById("close-size-guide");

opener.addEventListener("click", () => dialog.showModal());
closer.addEventListener("click", () => dialog.close());

// Send focus back to the button that opened the dialog.
dialog.addEventListener("close", () => opener.focus());

If you build a custom dialog or slide-out cart drawer instead, you have to provide all of these behaviors yourself.

Watch for keyboard traps

Third-party embeds are the usual suspects: chat widgets, video players, maps and payment frames. Tab into each one and make sure you can Tab or Shift+Tab out again. If leaving needs anything other than the standard keys, 2.1.2 No Keyboard Trap requires that you tell people how.

What won’t a keyboard test catch?

A keyboard test shows whether people can reach and operate everything. It doesn’t show what a screen reader announces, so a button with no accessible name can pass the keyboard test and still confuse someone who can’t see it. It also doesn’t check things like touch target size, color contrast or whether error messages make sense.

Automated tools are limited here too. They can flag some patterns, such as positive tabindex values, but they can’t judge whether the focus order makes sense, whether focus is visible in every state, or whether a dialog returns focus correctly. That’s why the five-minute manual test is worth repeating after every significant change.

Frequently asked questions

What is a keyboard trap?

A keyboard trap is a part of a page that keyboard focus can move into but not out of, such as a widget or embedded player that swallows the Tab key. WCAG success criterion 2.1.2 No Keyboard Trap, Level A, requires that focus can always be moved away using the keyboard, and that people are told how if it takes more than the standard keys.

Is removing the focus outline a WCAG failure?

It is if you remove the browser's focus indicator without providing your own. W3C documents this as failure F78 for success criterion 2.4.7 Focus Visible. Replacing the default outline with a clear custom style is fine.

What's the difference between :focus and :focus-visible?

Styles on :focus apply whenever an element has focus, including after a mouse click. Styles on :focus-visible apply when the browser decides a focus indicator is helpful, which always includes keyboard navigation and usually excludes mouse clicks on buttons and links.

Do I need a skip link?

Success criterion 2.4.1 Bypass Blocks, Level A, requires a way to skip blocks of content repeated across pages. A skip link to the main content is one of W3C's sufficient techniques, alongside headings and landmarks. It's simple to add and helps keyboard users directly.

Does tabindex=0 make a div accessible?

No. It makes the div focusable, but it doesn't give it a button role or make Enter and Space activate it. Use a native button or link element instead, which has all of that built in.

Sources