Tab order walkthrough
Press Tab on a web page and focus hops to the next link, button or field. This check writes out the whole route for any address and points to the controls that got left off it.
Focus is the keyboard’s cursor
A mouse can point anywhere. A keyboard can only be “on” one element at a time, and that position is called focus. Tab moves it forward, Shift+Tab moves it back, Enter follows a link, Space presses a button. People with tremors or limited hand movement get around this way. So do screen reader users, people on switch devices or voice control, and anyone who fills in forms fast.
The browser works out what can receive focus from the HTML alone. Links with an href, buttons, form fields, <summary>, media players with controls and frames are on the route by default, in the order they appear in the source. Everything else gets skipped unless a tabindex attribute says otherwise.
The three meanings of tabindex
| Value | Effect | When to use it |
|---|---|---|
tabindex="0" | Adds the element to the route at its natural position | Custom controls that can’t be a native <button> |
tabindex="-1" | Removes it from the route; scripts can still focus it | Inactive tabs, menu items reached with arrow keys, off-screen carousel slides |
tabindex="1" or more | Moves it ahead of everything else, lowest number first | Almost never. Focus jumps around the screen |
The walkthrough follows these rules to the letter: positive values first in ascending order, then source order. A group of radio buttons with the same name counts as one stop, since arrow keys move inside the group. Disabled controls, closed <dialog> elements, inert or hidden areas and the content of closed <details> are left out.
What the check flags
- Clickable elements the keyboard cannot reach
- A
<div onclick>or an element withrole="button"and notabindex. Mouse users can press it, and keyboard users can’t even get to it. Replace it with<button type="button">, which brings focus, Enter and Space for free. - Links without
href - To the browser, an
<a>with no destination is plain text, and Tab goes right past it. - Controls with
tabindex="-1" - Reported unless the link duplicates another reachable link to the same address, or belongs to a tab list, menu, toolbar or similar group where arrow keys take over.
- Skip link
- The check looks in the first three stops for a link to an anchor on the same page, such as
<a href="#content">Skip to content</a>, and confirms the targetidexists. It also counts how many presses it takes to get from the top of the page to<main>. With five or fewer, a missing skip link is only noted for information. - Focus outline
- We search the page’s
<style>blocks, inline styles and up to five style sheets from the same site for rules such asa:focus { outline: none }or* { outline: 0 }. If nothing replaces the outline with:focus-visibleor:focusstyles, the finding is red, because keyboard users can’t see where they are. - Other stops worth a look
- Focusable elements inside
aria-hidden="true", stops with no accessible name, frames without atitle, and links to#orjavascript:that act as buttons.
Bringing back a visible focus
Designers often remove the outline because it shows up after a mouse click and they find it ugly. The :focus-visible selector settles the argument. It only matches when the browser judges that the user needs to see focus, which in practice means keyboard use.
:focus-visible {
outline: 3px solid #1a5fb4;
outline-offset: 2px;
}WCAG 2.2 asks for an indicator with at least 3:1 contrast against the colors around it. The pattern :focus:not(:focus-visible) { outline: none } is safe and doesn’t get reported.
Limits of a static reading
The route is computed from the HTML our server receives. Click handlers attached in JavaScript with addEventListener leave no trace in the markup, so a <div> wired that way goes undetected. Menus built by script are invisible here too, and so are keyboard traps, where focus enters a widget and can’t get out. Style sheets on another domain, or beyond the fifth, aren’t read.
Finish with the real test, which takes two minutes. Put the mouse aside, load the page and press Tab until you reach the footer. You should always see where you are, and you should be able to open every menu and submit every form.
Questions people ask
How do I test keyboard navigation myself?
Click in the address bar, then press Tab over and over. Shift+Tab goes back, Enter works on links, Space on buttons and check boxes, arrow keys inside radio groups and menus, and Escape closes dialogs. On macOS Safari, turn on “Press Tab to highlight each item on a webpage” in the Advanced settings first.
Is tabindex="0" on a div enough to make it a button?
No. It makes the element focusable, but Enter and Space do nothing until you add key handlers, and a screen reader won’t call it a button without role="button". A native <button> gives you all three.
Do I need a skip link if my page has a main element?
Yes, for sighted keyboard users. Screen readers can jump to the <main> landmark with a shortcut. Someone using only the Tab key has no such shortcut and has to go through every menu item unless you offer a skip link.
Why does the Tab order not match what I see on screen?
Tab follows the order of the HTML source, not the visual layout. CSS such as order, flex-direction: row-reverse, grid placement or absolute positioning can move elements on screen without changing their place in the source. A positive tabindex has the same effect.
Is outline: none always an accessibility failure?
Only when nothing replaces it. A rule that removes the outline and sets a visible border, background or box shadow on focus in the same block is fine, and this check counts it as a replacement.