Lesson 01 · Keyboard · Breakdown

What was hiding in there.

The Northwind page held five barriers. Each one is shown below with the control, what it does to a keyboard user, and the one change that fixes it. How many did you find?

01 "Start free trial" cannot be reached

Where: hero, main action

You notice: Tab never lands on the main button. The most important action on the page is invisible to the keyboard.

Why: it is styled like a button but it is a <div>, which is not in the tab order and is not announced as a control. Swap it for a real <button>.

Causes the barrier
<div class="btn primary">Start free trial</div>
Fixes it
<button type="button" class="btn primary">Start free trial</button>

WCAG 2.1.1 Keyboard (Level A)

02 "Watch demo" is a link with no destination

Where: hero, secondary action

You notice: the action next to the trial button is also skipped by Tab, even though it looks like a normal link.

Why: it is an <a> with a click handler but no href. An anchor without an href is not focusable. Use a <button> for an action, or give the link a real href.

Causes the barrier
<a onclick="playDemo()">Watch demo</a>
Fixes it
<button type="button" onclick="playDemo()">Watch demo</button>

WCAG 2.1.1 Keyboard (Level A)

03 The billing toggle hides focus

Where: pricing, billing toggle

You notice: tabbing onto the Monthly / Yearly switch, nothing changes on screen. You cannot tell which option is focused.

Why: the focus outline was removed in CSS. The buttons still take focus, but with no visible ring a keyboard user is choosing blind. Restore a :focus-visible outline.

Causes the barrier
.toggle button:focus {
  outline: none;
}
Fixes it
.toggle button:focus-visible {
  outline: 3px solid #1364ff;
  outline-offset: 3px;
}

WCAG 2.4.7 Focus Visible (Level AA)

04 The currency menu will not open

Where: pricing, currency menu

You notice: the currency control never takes focus, and nothing you press opens it. The options inside are unreachable.

Why: the trigger is a <div> wired to click only, and the items are plain <div>s. A real menu needs a <button> trigger with aria-expanded, plus items that respond to the arrow keys and Esc.

Causes the barrier
<div onclick="toggle()">USD</div>
<ul hidden>
  <li><div>EUR, Euro</div></li>
</ul>
Fixes it
<button aria-haspopup="true"
        aria-expanded="false">USD</button>
<ul role="menu" hidden>
  <li role="none"><button role="menuitem">EUR, Euro</button></li>
</ul>

WCAG 2.1.1 Keyboard (Level A) and 4.1.2 Name, Role, Value (Level A)

05 The invite form tabs out of order

Where: request early access

You notice: the fields read Name, then Email, then the button, but Tab visits Email first, then the button, then back up to Name.

Why: positive tabindex values override the natural order. Any value above 0 is pulled to the front of the sequence. Remove them and let source order do the work.

Causes the barrier
<input name="name"  tabindex="3">
<input name="email" tabindex="1">
<button type="submit" tabindex="2">
Fixes it
<input name="name">
<input name="email">
<button type="submit">

WCAG 2.4.3 Focus Order (Level A)

Now see it done right

The same Northwind page, rebuilt so every one of these works with a keyboard.