QuestionsAccessibility

Keyboard navigation & focus order

Keyboard NavigationEasyAccessibility

What makes a UI keyboard-accessible? What does tabindex do, and why is a positive tabindex a smell?

What it tests

Whether every interaction works without a mouse, and how the tab order is really determined.

Approach & answer

A huge share of users navigate by keyboard alone — screen-reader users, people with motor impairments, power users. The baseline: everything you can do with a mouse must be doable with the keyboard, and the focus order must follow the visual/reading order. Native interactive elements (<a href>, <button>, <input>, <select>) are in the tab order automatically and handle their keys (Enter/Space to activate, arrows within selects); this is the #1 reason to use them instead of clickable <div>s, which are unreachable by Tab and deaf to Enter/Space until you wire it all up. tabindex controls focusability: tabindex="0" puts a normally non-focusable element (a custom widget root) into the natural tab order at its DOM position; tabindex="-1" makes an element focusable by script (element.focus()) but NOT by Tab — essential for moving focus to a heading or dialog programmatically. A POSITIVE tabindex (1, 2, 3…) is an anti-pattern: it yanks those elements to the FRONT of the tab order regardless of DOM position, creating a confusing sequence that's fragile and hard to maintain — fix the DOM order instead. Also never remove focusability from real controls, and ensure custom widgets implement their expected keys (Escape closes, arrows move within a composite). Focus order bugs usually trace to DOM order not matching visual order (e.g. CSS fl/grid reordering) — the fix is source order, not tabindex.

Use this technique when

Auditing a feature for keyboard support; explaining tabindex 0 vs -1 vs positive values.

Code

<!-- Unreachable by keyboard, deaf to Enter/Space -->
<div class="btn" onclick="go()">Go</div>

<!-- Native: in tab order + keyboard-operable for free -->
<button onclick="go()">Go</button>

<!-- tabindex 0: add a custom widget root to the natural order -->
<div role="slider" tabindex="0">…</div>

<!-- tabindex -1: focusable by script only (e.g. move focus here) -->
<h1 tabindex="-1" id="page-title">Results</h1>

<!-- SMELL: positive tabindex jumps ahead of DOM order -->
<input tabindex="3">

References