/ โ† Accessibility Fixes / Keyboard Navigation
In this guide
  1. What the keyboard rules in WCAG ask for
  2. Why sites fail with the keyboard
  3. How to test a page with the keyboard
  4. The fix, step by step
  5. Code patterns that work
  6. Composite widgets: tabs, sliders, carousels and grids
  7. Where the fixes live on common platforms
  8. Edge cases and questions of judgement
  9. Keeping it fixed
  10. Questions and answers

What the keyboard rules in WCAG ask for

Keyboard access is not one rule but a small family of them. Most are at level A, the most basic level of WCAG 2.2, which means a failure here is treated as a barrier rather than an inconvenience. The Accessibility Checker reports the ones it can detect in code, and the rest need a quick manual test.

Success criterionLevelIn plain English
2.1.1 KeyboardAEverything you can do with a mouse, you can also do with keys alone. Freehand drawing is the classic exception.
2.1.2 No Keyboard TrapAIf focus can move into something, it can move out again with standard keys, or the page tells you how.
2.1.4 Character Key ShortcutsASingle-letter shortcuts can be turned off or remapped, or only work when that control has focus.
2.4.1 Bypass BlocksAThere is a way to jump past repeated blocks such as the header and main menu.
2.4.3 Focus OrderAFocus moves in an order that keeps meaning and makes sense.
3.2.1 On FocusASimply landing on a control does not submit a form, open a new window or move you elsewhere.
2.4.7 Focus VisibleAAYou can see where focus is. Covered in its own guide.
2.4.11 Focus Not Obscured (Minimum)AAA sticky header, footer or banner never completely hides the focused item.

Visible focus styles have their own guide: fixing focus indicators. This page concentrates on whether focus can get somewhere, what happens when it arrives, and whether it can leave.

Why sites fail with the keyboard

Browsers give you keyboard support for free when you use the right HTML. A <button> is focusable and responds to Enter and Space. A link with an href is focusable and responds to Enter. Form fields, <select>, <details> and <summary> all work without a line of script. Keyboard failures nearly always come from replacing those elements with something that only listens for clicks.

How to test a page with the keyboard

An automated scan can spot a clickable <div> or a positive tabindex, but it cannot press keys and see what happens. The manual test takes about ten minutes per template and finds far more.

KeyWhat it should do
Tab / Shift+TabMove forward and back through links, buttons and fields.
EnterFollow a link, press a button, submit a form.
SpacePress a button, tick a checkbox, open a select list. Scrolls the page when nothing interactive has focus.
Arrow keysMove within a group: radio buttons, tabs, menus, sliders, select options.
EscapeClose a dialog, menu, tooltip or autocomplete list.
Home / EndJump to the first or last item in lists, tab sets and sliders that support it.

Mac users: switch Tab on first

On a Mac, Safari does not move to links with Tab out of the box. Turn on Press Tab to highlight each item on a webpage in Safari's Advanced settings, or hold Option while pressing Tab. Firefox and Chrome on macOS follow the system-wide Keyboard navigation switch in System Settings under Keyboard. If you skip this, you will think the whole site is broken when it is not.

Spotting hidden focus

When the outline disappears, open the browser console and run document.activeElement after each Tab. It returns the element that currently has focus, which quickly exposes a hidden mobile menu link or an off-screen slide. In Chrome you can also add a live expression for document.activeElement so it updates as you tab.

The fix, step by step

Step 1

Walk every key journey with the keyboard only

Pick the tasks that matter most, such as opening the main menu, searching, adding a product to the basket, booking, and sending the contact form. Put the mouse out of reach and complete each one using only Tab, Shift+Tab, Enter, Space, the arrow keys and Escape. Write down every control you could not reach, could not operate, or could not leave, together with the template it lives in, because the fix usually belongs in that template.

Step 2

Replace clickable divs with real buttons and links

Search the templates for click listeners attached to div, span, li and img elements. If the control goes to another page or address, make it an <a> with a real href. If it does something on the current page, such as opening a panel or adding to the basket, make it a <button type="button">. Reset the button styles in CSS so the design does not change, and delete any hand-written keydown handling the native element now makes redundant.

Step 3

Clean up tabindex and the tab order

Find every tabindex in the code. Change any value of 1 or higher to 0 or remove it, then fix the order by moving elements in the HTML rather than forcing it. Use tabindex="-1" only for things you focus with script, such as a heading after a route change. Check that CSS reordering with order, flex-direction: row-reverse or grid placement has not made the visual order disagree with the order focus follows.

Step 4

Make menus and dropdowns open from the keyboard

Give each submenu a real <button> next to or in place of the parent label, with aria-expanded set to false or true to match its state, and toggle the submenu when it is pressed. Keep the hover behaviour for mouse users if you like, but never make hover the only way in. Close the open submenu when Escape is pressed, return focus to its button, and close it when focus moves out of the menu.

Step 5

Contain focus in dialogs, then hand it back

Build modal pop-ups with the native <dialog> element opened by showModal(), which keeps focus inside, makes the page behind it inert and closes on Escape. If you cannot use it, add the inert attribute to the rest of the page while the pop-up is open. Move focus into the dialog when it opens, and when it closes send focus back to the control that opened it, so the visitor carries on from the same spot.

Step 6

Add a skip link past the header

Make the first focusable item on every page a link reading Skip to main content that points at #main, and put id="main" on the <main> element. Hide it off-screen until it receives focus, then slide it into view at the top left with strong contrast. Press Tab once on a fresh page load to confirm it appears, press Enter, then press Tab again and check that focus lands on the first link inside the content.

Step 7

Remove traps in embeds and third-party widgets

Tab into and out of every embedded map, video player, booking iframe, chat launcher, review carousel and cookie banner. If focus goes in but cannot come out with Tab or Shift+Tab, check the supplier's settings for an accessible mode, update to the current version, or report the fault and replace the widget. As a stopgap, place a clear link before the embed that lets keyboard users skip over it to the content that follows.

Code patterns that work

A button that looks like anything

Designers often avoid <button> because of the default grey box. Strip the defaults once and it can look like text, an icon or a card.

<button type="button" class="plain-btn" aria-expanded="false" aria-controls="filters">
  Filter results
</button>

.plain-btn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

A disclosure menu

<nav aria-label="Main">
  <ul>
    <li>
      <a href="/services/">Services</a>
      <button type="button" class="sub-toggle" aria-expanded="false" aria-controls="sub-services">
        <span class="visually-hidden">Show Services submenu</span>
      </button>
      <ul id="sub-services" hidden>
        <li><a href="/services/boilers/">Boiler servicing</a></li>
        <li><a href="/services/repairs/">Emergency repairs</a></li>
      </ul>
    </li>
  </ul>
</nav>

document.querySelectorAll('.sub-toggle').forEach(function (btn) {
  var panel = document.getElementById(btn.getAttribute('aria-controls'));
  btn.addEventListener('click', function () {
    var open = btn.getAttribute('aria-expanded') === 'true';
    btn.setAttribute('aria-expanded', String(!open));
    panel.hidden = open;
  });
  btn.parentElement.addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && !panel.hidden) {
      panel.hidden = true;
      btn.setAttribute('aria-expanded', 'false');
      btn.focus();
    }
  });
});

Using the hidden attribute (or display: none) matters: it removes the closed submenu links from the tab order, whereas hiding them with opacity or a negative position leaves them focusable. The button needs an accessible name; see fixing ARIA labels for the options.

A modal dialog

<button type="button" id="open-quote">Get a quote</button>

<dialog id="quote" aria-labelledby="quote-title">
  <h2 id="quote-title">Request a quote</h2>
  <form method="dialog">
    <!-- fields -->
    <button value="send">Send</button>
    <button value="cancel" formnovalidate>Cancel</button>
  </form>
</dialog>

var opener = document.getElementById('open-quote');
var dlg = document.getElementById('quote');
opener.addEventListener('click', function () { dlg.showModal(); });
dlg.addEventListener('close', function () { opener.focus(); });

Current browsers generally restore focus to the opener themselves when a modal dialog closes, but the explicit opener.focus() costs nothing and covers older versions and dialogs closed by script.

A skip link

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

.skip-link { position: absolute; left: -9999px; top: 0; }
.skip-link:focus { left: 1rem; top: 1rem; z-index: 1000;
  background: #111; color: #fff; padding: .75rem 1rem; }

The tabindex="-1" on <main> makes older browsers move the keyboard starting point along with the scroll position. It does not add <main> to the Tab sequence.

Composite widgets: tabs, sliders, carousels and grids

Some controls are groups rather than single buttons. The convention, set out in the W3C ARIA Authoring Practices Guide, is that Tab moves into the group once and out again, and the arrow keys move inside it. That keeps a set of twelve tabs or a date picker from costing twenty Tab presses.

๐Ÿ’ก Before building any composite widget, check whether a native element does the job. <details> and <summary> make an accordion with no script; <select> is a fully keyboard-accessible dropdown; <dialog> handles modal focus.

Where the fixes live on common platforms

The code changes above are the same everywhere, but where you make them differs.

Edge cases and questions of judgement

Text editors that use Tab

A rich text editor or code box may reasonably use Tab to indent. That is allowed under 2.1.2 as long as there is a way out that the page explains, for example a note saying that Ctrl+M switches the Tab key back to moving focus, or that Escape leaves the editor.

Focus on page load

Do not autofocus a search box or newsletter field on every page. It skips screen reader users past the page heading and can make mobile keyboards pop up unexpectedly. Autofocus is reasonable on a page whose only purpose is that field, such as a dedicated search page or a login screen.

Things that happen on focus

A select list that navigates to a new page as soon as the value changes is a trap for keyboard users, because pressing the down arrow to read the options fires the change. Add a Go button instead. The same applies to fields that submit the form when they receive focus.

Single-key shortcuts

If your site or web app uses single letters as shortcuts, such as J and K to move between items, speech recognition users can trigger them by accident when dictating. Offer a setting to turn them off or require a modifier key such as Ctrl.

Sticky headers and cookie banners

When a focused link slides under a fixed header or a cookie banner, WCAG 2.2 criterion 2.4.11 is failed. Adding scroll-padding-top to the html element equal to the header height usually solves the header case; banners should be dismissible and should not cover focused content while open.

Keeping it fixed

  1. Add a lint rule to the build that flags click listeners on non-interactive elements and positive tabindex values.
  2. Put a keyboard-only walk-through of the main journeys into your release checklist, especially after a theme update or a new plugin or app.
  3. Ask suppliers of widgets and apps whether their product has been tested with a keyboard and a screen reader before you install it.
  4. Run the Accessibility Audit on each template after changes and compare with the previous report.
  5. Keep a small library of known-good patterns, such as the button reset, disclosure menu, dialog and skip link above, and reuse them instead of rebuilding.
Questions and answers

1. How do I know if my website works with a keyboard?

Put the mouse away, click in the browser address bar and press Tab repeatedly. You should see focus move through every link, button and field in a sensible order, and you should be able to operate each one with Enter, Space or the arrow keys. Try the main menu, search, forms, pop-ups and the basket. If anything cannot be reached, cannot be used, or holds on to focus so you cannot leave, that is a keyboard failure to fix.

2. What is a keyboard trap?

A keyboard trap is any part of a page that focus can enter but cannot leave using standard keys such as Tab, Shift+Tab or Escape. Common examples are embedded booking forms in iframes, poorly built pop-ups and some chat widgets. WCAG 2.1.2 No Keyboard Trap is a level A criterion. A modal dialog that keeps focus inside on purpose is not a trap, provided Escape or a visible close button lets the visitor out.

3. Is tabindex="0" bad for accessibility?

No. A tabindex of zero adds an element to the normal tab order in its natural position, which is correct for a custom control that genuinely needs focus, such as a scrollable table wrapper. The problem value is anything of 1 or higher, which forces the element ahead of everything else and scrambles the order. A tabindex of minus one is also useful: it lets script focus an element without adding it to the Tab sequence.

4. Why can I not tab to links in Safari?

Because Safari on a Mac only moves between form fields with Tab by default. Links and buttons are skipped unless you turn on the setting called Press Tab to highlight each item on a webpage, found in the Advanced section of Safari settings, or hold Option while pressing Tab. This is a browser preference, not a fault on your site, so switch it on before testing, then judge the site as you would in any other browser.

5. Do I need a skip link if my site has good headings?

Yes, it is still worth adding. Headings and landmarks help screen reader users jump around, but people who use a keyboard without a screen reader cannot use those shortcuts, and they may face dozens of Tab presses through the header and menu on every page. A skip link to the main content is the simplest way to meet WCAG 2.4.1 Bypass Blocks for them, and it costs a few lines of HTML and CSS.

6. Should dropdown menus open on hover or on click?

Either can work for mouse users, but keyboard users need a control they can press. The most robust pattern is a real button beside each parent link that opens and closes its submenu and reports its state with aria-expanded, with Escape closing it again. You can keep hover opening for mouse users as well. Relying on the CSS focus-within selector alone is fragile, because the submenu links must still be reachable in a sensible order.

7. Does fixing keyboard access help screen reader users too?

Very often, yes. Screen readers on desktop computers are driven from the keyboard, and they rely on the same native buttons, links and form controls that make keyboard access work. Replacing a clickable div with a button fixes the Tab key and also gives the screen reader a role and a name to announce. Screen readers do have extra reading shortcuts, so test with one as well, but keyboard fixes are the foundation for both groups.

Find keyboard problems on your pages

The Accessibility Audit tests your page against WCAG 2.2 level AA and lists each finding with the element that failed and the success criterion behind it, so you can see which controls need fixing first.

Open Accessibility Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Accessibility Fixes  ·  Fixing Focus Indicators  ·  Fixing ARIA Labels  ·  Accessibility Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.