---
title: "Make everything work by keyboard · Agent Wranglers"
url: https://agentwranglers.se/playbook/website/accessibility/keyboard-navigation
updated: 2026-10-20
licence: "All rights reserved"
---

# Make everything work by keyboard

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Accessibility](https://agentwranglers.se/playbook/website/accessibility)
- Rule: Every link, button and control must work with just a keyboard. Tab moves in reading order, and focus never gets stuck. These are WCAG criteria 2.1.1, 2.1.2 and 2.4.3 (A).
- How: Use native links and buttons. Keep the source order the same as the visual reading order. Never use a positive `tabindex`. Give custom widgets the keys the WAI-ARIA Authoring Practices list, such as arrow keys in tabs and menus.
- Check: A browser test presses Tab through each page type and records every element that takes focus. Every visible link, button and field is reached, focus returns to the start, and axe `tabindex` passes.

Example:

```html
<!-- Good: native, focusable, works with Enter and Space -->
<button type="button">Open menu</button>
<!-- Bad: not focusable by role, no keys, jumps the tab order -->
<div onclick="openMenu()" tabindex="5">Open menu</div>
```

```js
// Playwright: Tab through the page and collect what took focus
const reached = new Set();
for (let i = 0; i < 300; i++) {
  await page.keyboard.press('Tab');
  const id = await page.evaluate(() => document.activeElement?.outerHTML.slice(0, 80));
  if (reached.has(id)) break;
  reached.add(id);
}
const controls = await page.locator('a[href]:visible, button:visible, input:visible, select:visible, textarea:visible').count();
expect(reached.size).toBeGreaterThanOrEqual(controls);
```

```python
# Playwright for Python: the same walk
reached = []
for _ in range(300):
    page.keyboard.press("Tab")
    key = page.evaluate("document.activeElement?.outerHTML.slice(0, 80)")
    if key in reached:
        break
    reached.append(key)
controls = page.locator("a[href]:visible, button:visible, input:visible, select:visible, textarea:visible").count()
assert len(reached) >= controls
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers website playbook: https://agentwranglers.se/playbook/website/accessibility/keyboard-navigation`.

- Previous: [Put the visible words in the name](https://agentwranglers.se/playbook/website/accessibility/label-in-name)
- Next: [Never take over single keys](https://agentwranglers.se/playbook/website/accessibility/character-key-shortcuts)
- [Accessibility](https://agentwranglers.se/playbook/website/accessibility)
