Keyboard navigation is not just about screen readers. It decides whether a site is usable for anyone without a mouse, temporarily or permanently, and it doubles as a robustness test for the whole interface. Visible focus and a coherent tab order form the most cost-effective accessibility baseline there is, and one of the most often neglected.
The keyboard reveals the structure
On a well-built page, the Tab key moves focus from one interactive element to the next, in the order they appear in the document. Native elements (an a with an href, button, form fields) are focusable with no configuration. A div with a click handler grafted onto it is not: it stays invisible to the keyboard until it is given a role and key handling.
Navigating a site by keyboard alone therefore exposes in seconds what no screenshot shows: a menu that cannot be reached, a phantom button built from a clickable image, a carousel that traps focus. The keyboard does not just test accessibility, it tests the soundness of the markup.
:focus-visible instead of removing the outline
The most widespread mistake fits in one declaration: outline: none, added to erase the blue ring judged unsightly on click. It strips keyboard users of the one cue that tells them where they are. The fix is not to restore the old outline everywhere, but to reserve it for the right context.
The :focus-visible pseudo-class lets the browser decide: it shows the ring when it judges that focus came from the keyboard, and hides it on a mouse click. The outline then appears only where it is useful.
/* Ne jamais retirer le contour sans le remplacer. */
:focus-visible {
outline: 3px solid #2d6cdf;
outline-offset: 2px;
border-radius: 2px;
}
/* Souris : pas d'anneau ; clavier : anneau visible. */
:focus:not(:focus-visible) {
outline: none;
}
/* Lien d'evitement : masque, revele au focus clavier. */
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
left: 1rem;
top: 1rem;
}
The rule applies to every interactive component, custom elements included. A ring of at least three pixels, clearly contrasted against the background and lifted off the element by an outline-offset, stays legible on most surfaces.
A site that cannot be navigated end to end by keyboard has a functional defect, not merely an accessibility one.
Tab order follows the DOM
The order in which focus visits elements is the source order, not the rendered one. A block moved visually with CSS (using order in a grid, for instance) keeps its original position in the tab sequence. When visual order and DOM order diverge, navigation becomes bewildering: it is better to fix the structure than to paper over it with the keyboard.
Two tools cover the vast majority of cases. The skip link, the first focusable element on the page, lets the user jump past navigation to reach the content. The tabindex="-1" attribute makes a target focusable programmatically without inserting it into the tab order.
<!-- Premier element focusable de la page -->
<a class="skip-link" href="#contenu">Aller au contenu</a>
<nav>…</nav>
<!-- tabindex="-1" rend la cible focusable par programme,
sans l'ajouter a l'ordre de tabulation. -->
<main id="contenu" tabindex="-1">
<h1>Titre de la page</h1>
</main>
One rule holds it together: never use a positive tabindex. Setting tabindex="1", "2" and so on forces an artificial order that desynchronises from the content at the first addition and becomes impossible to maintain. The only sane values are 0 (focusable in natural order) and -1 (focusable programmatically only).
Custom components, where it breaks
Dropdown menus, tabs, dialogs: as soon as a component moves away from native elements, keyboard handling becomes explicit. A modal dialog in particular must hold focus while it is open, otherwise the tab sequence wanders back into the page behind it, out of sight.
// Piege de focus minimal pour une boite de dialogue.
function piegerFocus(dialogue) {
const focusables = dialogue.querySelectorAll(
'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const premier = focusables[0];
const dernier = focusables[focusables.length - 1];
dialogue.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === premier) {
e.preventDefault();
dernier.focus();
} else if (!e.shiftKey && document.activeElement === dernier) {
e.preventDefault();
premier.focus();
}
});
}
The focus trap is only half the contract. On opening, focus should move to the dialog or its first field; on closing, it should return to the element that triggered it; the Escape key should close the dialog. Without this full cycle, the modal remains a trap for the keyboard user.
Forms: associated labels and focus on error
Forms concentrate most keyboard interactions, and a good share of the defects. Every field must be tied to an explicit label, through the for attribute pointing at the field’s id: clicking the label then focuses the field, and assistive technologies announce the right name. A placeholder does not replace a label, since it vanishes as soon as the user types.
On validation, focus should move to the first field in error, and the matching message should be tied to the field with aria-describedby. The keyboard user is led straight to the problem, without tabbing through the whole form again to find it.
Which selector for which need
| Selector | Fires when | Typical use |
|---|---|---|
:focus | the element receives focus, mouse or keyboard | too broad on its own, avoid for the ring |
:focus-visible | the browser judges focus to be from the keyboard | the focus ring, the default case |
:focus-within | a descendant has focus | highlighting a parent field or menu |
The thing to watch. outline: none with no visible replacement is the most common accessibility defect, and one of the easiest to fix. WCAG success criterion 2.4.7 requires a visible focus indicator; version 2.2 adds contrast and minimum-area requirements for that indicator. A quick check: unplug the mouse and go through the whole page by keyboard.
Key takeaways
- Keyboard navigation is a robustness test for the whole interface, not an option for a minority.
:focus-visiblereserves the focus ring for the keyboard without removing it for everyone.- Tab order follows the DOM; a skip link and
tabindexlimited to0and-1are enough. - Custom components need explicit focus handling, modal dialogs above all.
On my audits, the first test I run is always the same: I put the mouse away and I tab. In under a minute, half of a site’s accessibility defects surface, and they are rarely the most expensive to fix. Restoring a visible focus and straightening out the tab order often comes down to a few lines of CSS and a skip link. It is the best effort-to-payoff ratio I know of in this area. — Simon Janvier
Going further
Reference: :focus-visible on MDN Web Docs, and WCAG success criterion 2.4.7 “Focus Visible”.
