Keyboard Navigation
Every interactive element must be reachable and operable with only a keyboard — and focus must always be visible.
- Explain why keyboard accessibility matters beyond screen reader users
- Describe which elements are focusable by default and in what order
- Use tabindex correctly and explain why positive values are an anti-pattern
- Provide a visible focus indicator without breaking visual design
- Add a skip link to a page and explain when it is triggered
A mouse is optional. A keyboard is not. Every user who cannot or chooses not to use a pointing device relies on the keyboard to navigate the web — and that group is far larger than most developers expect. It includes people with motor disabilities, power users who keep their hands on the keyboard for speed, and anyone whose pointing device has broken, died, or been left at home.
WCAG Guideline 2.1 (Operable) requires that all functionality be available via keyboard. This lesson covers the concrete mechanics.
The default tab order
The browser provides keyboard navigation for free on a set of elements called focusable elements. These are interactive by design:
<a href="…">— links<button>— buttons<input>,<select>,<textarea>— form controls<details>— expandable disclosure widgets
When the user presses Tab, focus moves to the next focusable element in tab order. Tab order follows DOM source order by default — the order elements appear in your HTML file, reading top to bottom. Shift+Tab reverses direction.
This means your visual layout and your tab order must agree. If you use CSS to move a button visually before a link but the link appears first in the DOM, a keyboard user will encounter the link first while a mouse user sees the button first. That mismatch is disorienting.
tabindex: the escape hatch
The tabindex attribute controls whether a non-focusable element can receive
keyboard focus and where it sits in the sequence:
<!-- Makes a <div> focusable in source order — use sparingly -->
<div tabindex="0" role="button">Custom widget</div>
<!-- Removes from tab sequence but allows programmatic focus via JS -->
<div tabindex="-1" id="error-panel">Error: field is required.</div>
<!-- ANTI-PATTERN: positive values scramble the natural order -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>The rules in practice:
tabindex="0"— use when you have built a custom widget with no native HTML equivalent. The element enters the tab sequence at its DOM position.tabindex="-1"— use when you need JavaScript to call.focus()on an element programmatically (like moving focus to an error message) without putting it in the tab sequence.- Positive
tabindex— avoid entirely. It overrides source order and creates a navigation sequence that is disconnected from the visual layout. Fix the DOM order instead.
The cleanest solution is almost always to put elements in the DOM in the
order you want them reached by keyboard. Reach for tabindex only when
that is not possible.
Focus styles: visible focus is not optional
When an element receives focus it should be visually obvious. This is the focus indicator — the outline or highlight ring browsers apply by default. Many developers remove it because it looks unstyled:
/* DO NOT do this without providing a replacement */
* {
outline: none;
}Removing the outline without a replacement fails WCAG 2.1 Success Criterion 2.4.7 (Focus Visible). Keyboard users lose all ability to track where they are on the page.
The modern approach uses :focus-visible instead of :focus. :focus-visible
applies only when the browser determines focus was reached via keyboard, not
via a mouse click. This lets you suppress the ring for mouse users (who find
it distracting on click) while preserving it for keyboard users (who need it):
/* Remove the default for mouse users */
:focus {
outline: none;
}
/* Restore a well-designed indicator for keyboard users */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
border-radius: 2px;
}The focus indicator you provide must meet a minimum contrast ratio of 3:1 against adjacent colours (WCAG 2.1 AA for non-text elements). A thin 1px outline in a mid-grey will fail.
Skip links
On a page with substantial navigation — a top nav, a sidebar, a breadcrumb — a keyboard user must Tab through every single item before reaching the main content. On a large nav, that can be thirty or more keystrokes per page load.
A skip link solves this. It is an anchor tag that
targets the <main> element, placed as the very first focusable item in the
document. It is visually hidden by default but becomes visible when it receives
focus:
<!-- First element inside <body> -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- ... navigation ... -->
<main id="main-content">
<!-- page content -->
</main>.skip-link {
position: absolute;
top: -999px;
left: 0;
padding: 8px 16px;
background: #005fcc;
color: #fff;
font-weight: bold;
z-index: 1000;
}
.skip-link:focus {
top: 0;
}When a keyboard user lands on the page and presses Tab, the skip link appears.
Pressing Enter or Space activates it and focus jumps to <main>. The nav is
bypassed entirely.
Interactive elements: use the right element
The single most important rule for keyboard accessibility: use <button> for
actions, <a> for navigation. Never substitute <div>, <span>, or other
non-interactive elements when a native interactive one exists.
<!-- Wrong: not focusable, not keyboard-operable, not announced as a button -->
<div class="btn" onclick="handleClick()">Submit</div>
<!-- Right: focusable, activates with Enter/Space, announced as "Submit, button" -->
<button type="button" onclick="handleClick()">Submit</button>The <div> version requires you to add role="button", tabindex="0", a
keydown handler for Enter and Space, and any relevant state attributes
(aria-pressed, aria-disabled) — all to recreate what <button> provides
natively for free.
If you find yourself adding tabindex="0" and role="button" to a <div>,
stop and switch to <button>. The native element is always preferable. The
only legitimate use of <div role="button"> is inside a framework component
that cannot render arbitrary HTML elements.
Focus traps: intentional vs accidental
A focus trap inside a modal dialog is intentional and correct. When a dialog is open, focus should cycle through the dialog's controls — the user should not be able to Tab to content behind the overlay, because that content is visually and semantically hidden.
A focus trap anywhere else — a form where Tab stops working, a dropdown that swallows all keystrokes — is a bug. WCAG 2.1 SC 2.1.2 (No Keyboard Trap) requires that users can always move focus away from any component using standard keys.
Where to go next
You can now make any page navigable by keyboard and visually track focus throughout. Next: ARIA Roles and Attributes — the attribute set that communicates role, state, and properties to assistive technologies when native HTML elements are not enough.
What Is Web Accessibility?
Accessibility means building pages that work for all users — including those using screen readers, keyboard-only navigation, or other assistive technologies.
ARIA Roles and Attributes
ARIA adds semantic meaning to elements that HTML alone can't express — but the first rule of ARIA is to not use it if native HTML already does the job.