slop audit
Blog post 21 / Accessibility and interaction

Keyboard accessibility testing: developer checklist

Use this keyboard accessibility testing checklist to verify focus order, visible focus, controls, dialogs, and keyboard traps before you launch a website.

By Slop Audit · 8 min read

Keyboard accessibility testing checks whether a visitor can reach, identify, operate, and leave each interactive part of a page without a mouse. Test the deployed route with Tab, Shift+Tab, Enter, Space, arrow keys, and Escape. Record the focus order, the visible focus indicator, and any point where the task stops.

The W3C's WCAG keyboard guidance says page functionality must work through a keyboard interface, apart from actions that depend on a movement path, such as freehand drawing. Keyboard access supports people who use screen readers, switch devices, voice input, or keyboards because a pointer does not meet their needs.

Keyboard accessibility testing checklist

Run this pass on the release-critical route:

  1. Reload the page and keep the pointer out of the test.
  2. Press Tab through the header, main content, controls, and footer.
  3. Press Shift+Tab to confirm that focus can move back.
  4. Check that the focused control has a visible indicator and stays on screen.
  5. Activate links with Enter and buttons with Enter and Space.
  6. Use arrow keys inside widgets that define arrow-key behavior.
  7. Open each menu or dialog on the main task, then close it with the documented key.
  8. Complete the page's main task without switching to a pointer.
  9. Repeat the path at a narrow viewport and with any sticky banner open.
  10. Save the route, browser, viewport, build, and first failed step.

Start with the full user task. A tab sequence can look clean while the submit button ignores Enter, a modal keeps focus after it closes, or a custom menu requires a click.

Test the deployed keyboard path

Use the public build because routing, consent banners, authentication prompts, and third-party scripts can change the focus path. Begin at the browser address bar, load the route, and press Tab once. The first useful stop should match the page structure. A skip link can come first, followed by navigation and the primary task. Use the skip link implementation and testing guide to verify its target focus, CSS reveal, and next Tab stop.

Track four facts for each stop:

  • The control receives focus.
  • You can see which control has focus.
  • The order matches the reading and task order.
  • The expected key performs the action.

Continue past the primary action. Reaching the first button proves little if a chat launcher, code editor, carousel, or embedded frame traps focus later. W3C defines a keyboard trap as a component that accepts keyboard focus but does not let the user move away through a keyboard interface. Test both directions and record the component where focus stops or repeats.

Use the website form testing checklist for validation, error recovery, and submission states. The keyboard pass should include an invalid submission and the success path, not a tour of empty fields.

Use native controls and source order

Native HTML gives links, buttons, inputs, and selects established keyboard behavior. Prefer a button for an action and an a element with an href for navigation. A clickable div needs extra focus, keyboard, role, name, and state code that native elements already provide.

<!-- Native control: focus and activation come from the browser. -->
<button type="button" aria-expanded="false" aria-controls="filters">
  Show filters
</button>

Keep the DOM order aligned with the visual and reading order. CSS grid and flexbox can place a control in a different visual position without changing sequential focus. Positive tabindex values create a second ordering system that can break after a component moves. The MDN keyboard accessibility guide recommends source order and warns against tabindex values above zero.

Use tabindex="0" when a custom interactive element must join the natural sequence. Use tabindex="-1" when code must move focus to an element, such as the main region after a skip link, without adding it to the normal Tab order. Keep static headings and containers out of the sequence unless focus serves a defined task.

Make the focus indicator visible

Sighted keyboard users need to see the active control. WCAG 2.2 Focus Visible requires a mode where the keyboard focus indicator remains visible. Do not remove the browser outline unless your replacement works across the page's light, dark, and image-backed surfaces.

:where(a, button, input, select, textarea):focus-visible {
  outline: 3px solid #0b6bcb;
  outline-offset: 3px;
}

Inspect the indicator rather than trusting the stylesheet. Check links, icon buttons, fields, cards with actions, and controls inside overlays. A border color change can disappear against an adjacent panel. A thin shadow can sit under a sticky header.

WCAG 2.2 also adds Focus Not Obscured: author-created content must not hide the focused component in full. Test cookie notices, fixed navigation, chat buttons, and open drawers at a narrow width. Pair this pass with mobile website testing because a focus ring outside the viewport gives the user no useful location cue.

Test dialogs and composite widgets

Menus, tabs, listboxes, grids, and dialogs use more than Tab. Follow the keyboard interaction pattern for the component you built. A tab list often uses arrow keys inside one Tab stop; adding each tab to the page sequence can make navigation slow and contradict the expected pattern.

Dialog testing needs a full focus cycle:

  1. Activate the trigger with the keyboard.
  2. Confirm that focus moves inside the dialog.
  3. Use Tab and Shift+Tab through the dialog controls.
  4. Close the dialog with Escape when the design supports dismissal.
  5. Confirm that focus returns to the trigger or the next logical task control.

The W3C modal dialog pattern keeps the dialog's Tab sequence inside the open modal, closes it with Escape, and returns focus after close. That temporary focus loop is part of modal behavior. The user must have a clear keyboard command that closes the dialog, or the intended loop turns into a trap.

Test custom components against their WAI-ARIA pattern and with assistive technology. A generic automated scan cannot decide whether arrow-key behavior, announcements, and task order make sense together.

Add Playwright keyboard testing to CI

Automate the stable parts of the primary path. Playwright's keyboard API can send Tab, Enter, Escape, arrow keys, and modifier combinations to the rendered page.

import { expect, test } from "@playwright/test";

test("completes the primary dialog flow with a keyboard", async ({ page }) => {
  await page.goto("https://example.com/pricing");

  const trigger = page.getByRole("button", { name: "Compare plans" });
  await trigger.focus();
  await expect(trigger).toBeFocused();

  await page.keyboard.press("Enter");
  const dialog = page.getByRole("dialog", { name: "Compare plans" });
  await expect(dialog).toBeVisible();
  await expect(dialog.getByRole("button", { name: "Choose Starter" }))
    .toBeFocused();

  await page.keyboard.press("Escape");
  await expect(dialog).toBeHidden();
  await expect(trigger).toBeFocused();
});

Keep the test tied to a user task and accessible names. Avoid assertions about a long page-wide focus sequence when navigation or footer links change often. Add the test to your website regression testing set, then run a short manual pass for focus visibility, order, and unexpected overlays before release.

Interpret automated keyboard evidence

Automation can confirm that a browser moved focus, identify the active element, and compare computed focus styles. It can detect a repeated target that suggests a trap. It cannot prove that the full order makes sense, that each custom widget follows its expected key map, or that a person can finish the task with assistive technology.

Treat a clean automated result as evidence for the path the tool ran. Keep manual keyboard and screen-reader review in the release process. The broader accessibility checks before launch cover labels, headings, contrast, target size, and the limits of automated testing.

Run keyboard accessibility testing with Slop Audit

Run Slop Audit on the public URL before launch. Its Browser Run presses Tab through a bounded sequence, records reached controls, checks for a visible :focus-visible outline or box shadow, and reports repeated focus that may indicate a keyboard trap. The report keeps the observed counts and mobile viewport evidence with each finding so you can retest after a fix.

Slop Audit samples up to 12 forward Tab presses on one public page. It does not run Shift+Tab, activate each control, test every widget key, inspect private routes, or certify WCAG conformance. Use the report to find a broken starting path, then complete the manual and Playwright checks in this guide.

Finish with the website launch checklist. Save the scan report and the release identifier so the next regression has a concrete baseline.