Accessibility Required
Make everything work by keyboard
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 to do it
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.
Example
<!-- 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>
// 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);
# 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
How to 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.
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.