Keyboard-Only Testing
The single most powerful accessibility test costs nothing and takes one action: unplug the mouse. A huge share of real failures surface the moment you try to drive the page with the Tab key alone.
1 The Hook
A tester on an AoG service portal was handed a slick new “quick apply” modal. Click a button, a dialog slides up over the page, you fill three fields and submit. With a mouse it was flawless. The tester did one thing the designer never had: they put the mouse to one side and pressed Tab.
The focus ring vanished into the page behind the modal. Tab again — it was now somewhere down in the page underneath the dialog, invisible, while the modal sat on top. They could hear the page scrolling but had no idea where focus was. They could not reach the submit button, could not close the dialog, could not get back out. A keyboard user opening that modal would be stuck in it, unable to finish and unable to leave.
This is the most common and most serious accessibility failure there is, and a mouse will never reveal it. A keyboard user — someone with a motor disability, a person using a switch device, a power user, anyone whose mouse just died — moves through a page in a strict linear order using Tab, Shift+Tab, arrow keys, Enter, Space, and Escape. If your component does not cooperate with those keys, it does not exist for them.
The good news is that the test is free and fast. You do not need a screen reader or a lab. You need a keyboard, the discipline to leave the mouse alone, and a short list of things to watch for. This lesson is that list.
2 The Rule
If you cannot do it with the keyboard alone, it is broken. Every action a mouse can perform must be reachable and operable with Tab, Shift+Tab, arrows, Enter, Space, and Escape — in a sensible order, with focus always visible, and never trapping the user. Unplug the mouse and try to complete the task: that one test finds more real failures than any scanner.
3 The Analogy
Navigating a building in the dark by touch.
Imagine moving through an unfamiliar building in a power cut, finding your way by running a hand along the wall from one doorway to the next. You can only go where the wall leads, in the order it leads you. If someone has left a chair in the corridor, you walk into it. If a door opens into a room with no other exit and the handle is missing, you are trapped. If the rooms are connected in a confusing order, you get lost even though every room is fine on its own.
A keyboard user moves through a page exactly like that — one focusable element to the next, in the order the page defines, with no overview. The visible focus indicator is their hand on the wall: take it away and they have no idea where they are. A keyboard trap is the room with no exit. Keyboard testing is walking the building in the dark yourself and noticing every chair in the corridor.
4 Tab Order and Focus Order
Press Tab and focus moves to the next interactive element; press Shift+Tab and it moves back. The sequence this follows is the focus order, and WCAG 2.4.3 Focus Order requires it to preserve meaning and operability. By default the browser follows the order elements appear in the DOM — the source code — not the order they appear on screen. When CSS rearranges things visually but the DOM stays put, the two diverge and focus jumps around unpredictably.
What to test:
- Logical sequence: tab from the top of the page to the bottom and confirm focus moves in the order a sighted user would read — not from the header, down to the footer, then back up to a form. On a Waka Kotahi form, focus should walk the fields in the order they are asked.
- Everything interactive is reachable: every link, button, field, checkbox, and custom control receives focus at some point. If you tab through the whole page and a control is skipped, a keyboard user cannot use it.
- Nothing dead is focusable: Tab should not land on plain text, a layout
<div>, or a disabled control. Focus stopping on things you cannot act on wastes the user and signals atabindexmisused. - No positive tabindex:
tabindex="1"or higher forces a custom order that almost always breaks the natural flow. Watch for focus that leaps to one field first regardless of position — the sign of a positive tabindex.
5 The Visible Focus Indicator
The focus indicator is the outline or highlight that shows which element currently has focus. For a keyboard user it is the cursor — the one piece of feedback telling them where they are. WCAG 2.4.7 Focus Visible (AA) requires it to be present; WCAG 2.2 added 2.4.13 Focus Appearance, which sets expectations on how clear it must be, and 2.4.11 Focus Not Obscured, which says the focused element must not be hidden behind other content.
The classic failure is a stylesheet that contains *:focus { outline: none; } — added because a designer thought the default outline looked untidy, with nothing put back in its place. Now focus moves invisibly and the keyboard user is navigating blind.
What to test:
- Always visible: tab through every interactive element and confirm a clear visual change marks the focused one. No element should be focusable with no indicator (2.4.7).
- Clear enough: the indicator should have decent contrast and size, not a faint one-pixel line a sighted-but-low-vision user would miss (2.4.13).
- Never obscured: as you tab, watch whether the focused element ever disappears under a sticky header, a cookie banner, or a floating chat widget. If it does, that is a 2.4.11 Focus Not Obscured failure — new in 2.2 and easy to miss.
6 Keyboard Traps and Focus Management
A keyboard trap is any place where focus goes in and cannot get out with the keyboard. WCAG 2.1.2 No Keyboard Trap forbids them outright — it is one of the few criteria where a single failure can make a whole page unusable, because the user is stuck and cannot reach anything else.
The opposite problem is poor focus management: a component that opens but does not move focus to itself, or closes and dumps focus somewhere random. Modals are where both failures cluster, so a modal is the perfect thing to test. A correctly behaved dialog does four things:
- Moves focus in: when the modal opens, focus moves into it (to the dialog, the first field, or the close button) — not left behind on the page underneath.
- Traps focus inside — deliberately: while the modal is open, Tab and Shift+Tab cycle only through the modal’s controls and do not leak out to the page behind it. This is the one place trapping is correct, and it must loop, not dead-end.
- Closes on Escape: pressing Escape dismisses the dialog. A modal you cannot Escape out of is the AoG portal failure from the Hook.
- Returns focus out: when the modal closes, focus returns to the control that opened it, so the user picks up where they left off rather than at the top of the page.
Test a modal by running those four in order with the mouse unplugged: open it (focus moves in), tab around (stays inside and loops), press Escape (it closes), and check where focus lands (back on the trigger). A modal that fails any of the four has a focus-management defect, and one that you cannot Escape or tab out of is a 2.1.2 keyboard trap.
7 Skip Links
Every page repeats the same header and navigation. A sighted user’s eye jumps straight past it to the content; a keyboard user has to Tab through every nav link on every page before reaching the main content. A skip link — a “Skip to content” link as the very first focusable element — lets them jump the repeated block in one keystroke. WCAG 2.4.1 Bypass Blocks requires a mechanism to do this.
What to test:
- It exists and is first: load the page and press Tab once. The first focusable element should be the skip link.
- It becomes visible on focus: skip links are often hidden until focused. When you Tab to it, it must appear — a skip link you cannot see when focused is no use.
- It actually works: press Enter on it and confirm focus jumps to the main content, past the navigation. A skip link that moves the visual scroll but leaves focus in the nav has not done its job.
This is one of the rare accessibility patterns a sighted mouse user never encounters, which is exactly why it gets shipped broken. The skip link on this very page — the “Skip to content” you can Tab to right now — is the pattern in action.
8 The ARIA Pitfalls
ARIA (Accessible Rich Internet Applications) is a set of attributes that add accessibility information to markup — roles, states, and properties. It is powerful and it is dangerous, because ARIA changes what assistive technology announces but does not add any keyboard behaviour. The first rule of ARIA is that no ARIA is better than bad ARIA. These are the pitfalls a keyboard tester catches:
role="button"on a div with no keyboard handler: the div now announces as a button but still cannot be focused or activated with Enter/Space. ARIA promised a button; the keyboard delivers nothing. A real<button>would have given both for free.- Custom widgets that ignore expected keys: a div-based dropdown with
role="listbox"is expected to respond to arrow keys, Enter, and Escape. If it only responds to clicks, the role is a lie — it claims a behaviour it does not have. aria-hidden="true"over something focusable: hiding an element from assistive technology while it is still in the tab order produces a “phantom stop” — focus lands on something the screen reader refuses to announce.- Reinventing native elements: a hand-built checkbox or select that has to re-implement focus, keys, and state, and gets one of them wrong. The native
<input>and<select>already handle the keyboard correctly.
<button>, <a>, <input>, or <select> gets keyboard operability, focusability, and the correct role with zero extra code. Most ARIA keyboard failures are native elements that someone rebuilt by hand — and broke.9 Common Mistakes
🚫 Testing the modal with the mouse and assuming the keyboard is fine
Why it happens: Everything works on click, so the component feels done.
The fix: The mouse hides every keyboard failure — the lost focus, the trap, the unreachable submit. Put the mouse down and run the four modal checks: focus in, traps and loops inside, Escape closes, focus returns. That is the only way to see the failures.
🚫 Removing the focus outline because it “looks bad”
Why it happens: A designer dislikes the default outline and adds outline:none globally.
The fix: That blinds every keyboard user (2.4.7 Focus Visible). If the default style is unwanted, replace it with a custom indicator — never just remove it. Tab through the page and confirm focus is always visible.
🚫 Trusting role="button" to make a div operable
Why it happens: ARIA looks like it “turns” the div into a button.
The fix: ARIA changes what is announced, not what the keyboard does. The div still is not focusable and does not respond to Enter/Space. Use a real <button>, or the control is unusable from the keyboard (4.1.2, 2.1.1).
🚫 Checking the skip link exists in the HTML but never operating it
Why it happens: Seeing the <a> in the source looks like a pass.
The fix: A skip link can exist, be invisible on focus, or fail to move focus to the content. Tab to it, confirm it appears, press Enter, and confirm focus actually lands in the main content (2.4.1 Bypass Blocks). Existence is not operation.
10 Now You Try
Three graded exercises: spot the trap, fix the modal, build a test script. Write your answer, run it for AI feedback, then compare to the model answer.
Below is a description of a RealMe-style session-timeout warning dialog. Identify 3 keyboard problems, and for each name the numbered WCAG criterion it breaks. At least one must be the keyboard trap.
After two minutes idle, a dialog appears over the page with “Stay signed in” and “Sign out” buttons. When it opens, focus stays on whatever field the user was last in, behind the dialog. There is no Escape handler — pressing Escape does nothing. While the dialog is open, pressing Tab moves focus down into the page behind it and never reaches the two dialog buttons. The two buttons are styled
<span> elements with click handlers and no focus outline.
List 3 keyboard problems, each with its numbered criterion:
Show model answer
There are at least four real problems; any three correctly named earn full marks, and at least one must be the trap. 1. Keyboard trap / focus leaks behind the dialog — Criterion: 2.1.2 No Keyboard Trap (A), and 2.4.3 Focus Order (A). Tab moves into the page behind the open dialog instead of cycling through its controls, so the user can never reach "Stay signed in" or "Sign out" and is effectively stuck. (A true trap is focus that cannot escape; here the related failure is that focus is mismanaged so the user cannot reach the dialog at all — call out both.) 2. Focus not moved into the dialog on open — Criterion: 2.4.3 Focus Order (A). When the dialog opens, focus should move into it; instead it stays on the field behind, so a keyboard user does not even know the dialog has focusable controls. 3. No Escape handler — Criterion: 2.1.2 No Keyboard Trap (A) supports this; a modal should be dismissible from the keyboard. With no Escape and no reachable button, there is no keyboard way out. 4. Buttons are spans with click handlers and no focus outline — Criterion: 4.1.2 Name, Role, Value (A), 2.1.1 Keyboard (A), and 2.4.7 Focus Visible (AA). Spans are not focusable or operable with Enter/Space and show no focus indicator, so even if reachable they could not be used. The fix in one line: move focus into the dialog on open, trap-and-loop focus inside it, close on Escape, return focus to the trigger on close, and use real
A developer asks you to spell out the correct keyboard and focus behaviour for a modal so they can fix it. Write a clear specification covering focus on open, focus while open, Escape, focus on close, and the focus indicator, for a fictional Te Whatu Ora “cancel appointment” confirmation modal.
“The modal opens, focus stays on the page behind it, Tab leaks to the page, Escape does nothing, and when you close it focus jumps to the top of the page.”
Write the correct behaviour spec:
Show model answer
On open (focus): When the modal opens, move focus into it — to the first interactive element or the dialog container with an accessible name. Do not leave focus on the page behind. While open (Tab / Shift+Tab): Trap focus inside the modal. Tab and Shift+Tab cycle only through the modal's focusable controls (the "Keep appointment" and "Cancel appointment" buttons and the close button) and loop — from the last control Tab returns to the first. Focus never reaches the page behind, which should be inert/aria-hidden. Escape key: Pressing Escape closes the modal (equivalent to "Keep appointment" / dismiss), so the user is never stuck. On close (focus returns to): Focus returns to the control that opened the modal — the "Cancel appointment" trigger button in the appointment row — not to the top of the page, so the user resumes where they were. Focus indicator: Every control inside the modal shows a clearly visible focus indicator with good contrast; the default outline is kept or replaced, never removed. WCAG criteria this satisfies: 2.1.2 No Keyboard Trap (A) — Escape and looping focus give a way out; 2.4.3 Focus Order (A) — focus moves in on open and returns on close; 2.4.7 Focus Visible (AA) — visible indicator; 2.4.11 Focus Not Obscured (AA) — the focused control is not hidden behind other content. A strong answer covers all five behaviours AND ties them to the right criteria. Missing the "return focus to the trigger" step is the most common gap.
Write a keyboard-only test script of 5 steps for a fictional Waka Kotahi vehicle-licence renewal flow (header with nav, a skip link, a multi-field form, a custom date dropdown, and a confirmation modal). Each step: the keys pressed, what you observe, and the pass condition. Cover skip link, tab order, focus visibility, the custom dropdown, and the modal.
Show model answer
KB-01 (skip link) | Keys: load page, press Tab once | Observe: the first focusable element is a "Skip to content" link that becomes visible on focus | Pass condition: skip link is first, visible when focused, and pressing Enter moves focus into the main content past the nav (2.4.1) KB-02 (tab order) | Keys: Tab from top to bottom of the form | Observe: focus moves through skip link, nav, then each form field in reading order, with no dead stops on plain text | Pass condition: order is logical, every control is reachable, nothing non-interactive takes focus (2.4.3) KB-03 (focus visible) | Keys: Tab onto each field and button | Observe: a clear focus indicator appears on each, never hidden behind the sticky header | Pass condition: focus is always visible (2.4.7) and not obscured (2.4.11) KB-04 (custom date dropdown) | Keys: Tab to the dropdown, press Enter/Space to open, arrow keys to move, Enter to select, Escape to close | Observe: it opens, arrows move the selection, Enter chooses, Escape closes and returns focus to the trigger | Pass condition: fully operable by keyboard with expected keys and no trap (2.1.1, 2.1.2) KB-05 (confirmation modal) | Keys: activate submit to open the modal, Tab/Shift+Tab inside it, press Escape | Observe: focus moves into the modal on open, cycles only within it and loops, Escape closes it, focus returns to the submit trigger | Pass condition: focus managed in/out, trapped-and-looping inside, dismissible by Escape (2.1.2, 2.4.3) A strong script: each step names the actual keys, states an observation a tester can verify, has a pass condition tied to a criterion, and together the five cover skip link, order, visibility, custom widget, and modal. Weak scripts say "check the keyboard works" with no keys or criteria.
11 Self-Check
Click each question to reveal the answer.
Q1: What is the single most valuable keyboard accessibility test, and what does it find?
Unplug the mouse and try to complete the task using Tab, Shift+Tab, arrows, Enter, Space, and Escape only. It surfaces the highest-impact failures — unreachable controls, keyboard traps, lost focus, and invisible focus indicators — that a mouse can never reveal, and it costs nothing.
Q2: A modal should do four things with focus. What are they?
Move focus into the modal on open; trap and loop focus inside it while open so Tab cannot leak to the page behind; close on Escape; and return focus to the control that opened it on close. Failing any of the four is a focus-management defect; being unable to Escape or tab out is a 2.1.2 keyboard trap.
Q3: Why does adding role="button" to a div not make it keyboard-operable?
ARIA changes what assistive technology announces, not what the keyboard does. The div will be announced as a button but is still not focusable and does not respond to Enter or Space, because ARIA adds no behaviour. A native <button> provides the role, focusability, and keyboard handling together. No ARIA beats bad ARIA.
Q4: How do you properly test a skip link, and which criterion does it serve?
Load the page, press Tab once and confirm the skip link is the first focusable element and becomes visible on focus, then press Enter and confirm focus actually moves into the main content past the navigation. It serves 2.4.1 Bypass Blocks. Checking it exists in the HTML is not enough — it must be operated.
Q5: What new-in-2.2 criterion do you check by watching focus as you tab, and what is the failure?
2.4.11 Focus Not Obscured (Minimum), AA. As you tab, the focused element must never be entirely hidden behind a sticky header, cookie banner, or floating widget. If the thing with focus disappears under fixed furniture, that is the failure — and it is easy to miss because nothing looks wrong until you tab to that spot.
12 Interview Prep
Real questions asked in NZ QA interviews for roles on public-facing and government systems. Read the model answers, then practise your own version.
“If you had time for only one accessibility test on a new component, what would it be?”
Keyboard-only testing — I’d put the mouse aside and try to complete the task with Tab, Shift+Tab, arrows, Enter, Space, and Escape. It is free, it is fast, and it finds the highest-impact failures: controls you cannot reach, keyboard traps where focus goes in and cannot get out, focus that disappears, and missing focus indicators. Those are the failures that completely lock a keyboard user out of a task, and a mouse hides every one of them. A clean automated scan tells me nothing about any of it.
“How do you test a modal dialog for accessibility?”
With the mouse unplugged I run four checks. One, when it opens, does focus move into the modal rather than staying on the page behind? Two, while it is open, do Tab and Shift+Tab cycle only through the modal’s controls and loop, instead of leaking to the page behind? Three, does Escape close it? Four, when it closes, does focus return to the control that opened it? If it fails any of those it has a focus-management defect, and if I cannot tab out or Escape it is a 2.1.2 keyboard trap — the most serious one, because the user is stuck and can’t reach anything else on the page.
“A developer says they made a div accessible by adding role="button". What do you check?”
I’d explain that role="button" only changes what a screen reader announces — it adds no keyboard behaviour at all. So I’d test it from the keyboard: can I Tab to the div, and does pressing Enter or Space activate it? A plain div is not focusable and has no key handlers, so it will usually fail both, which breaks 2.1.1 Keyboard and 4.1.2 Name, Role, Value. The right fix is almost always a native <button>, which gives the role, focusability, and keyboard activation for free. The principle is that no ARIA is better than bad ARIA.