Leçon 3/7 10 min

Clavier et focus visible

Pose ta souris et navigue au Tab : l'ordre du DOM, le crime du outline:none, :focus-visible, le skip link et les nouveaux critères WCAG 2.2.

Pose ta souris. Maintenant, débrouille-toi.

Qui navigue un site sans souris ? Plus de monde que tu ne crois. La personne avec un handicap moteur qui ne peut pas viser un curseur. La personne malvoyante, dont le lecteur d'écran pilote tout au clavier. Le power user qui va trois fois plus vite au Tab. Et toi, le jour où ta souris rend l'âme en plein rush.

Pour tous ceux-là, le clavier n'est pas un confort : c'est la seule porte d'entrée. À la leçon 2, on a vu que le HTML sémantique fait 80 % du travail. Le clavier, c'est là que ces 80 % se vérifient ou s'effondrent.

Le test de 30 secondes : sur ta page, lâche la souris. Appuie sur Tab, encore, encore. Trois questions, en continu : peux-tu atteindre chaque lien, chaque bouton, chaque champ ? Peux-tu les actionner (Entrée, Espace) ? Et surtout : sais-tu en permanence où tu te trouves ? Si la réponse est non une seule fois, un utilisateur clavier est bloqué. Ce test ne coûte rien et révèle presque tout.

La mécanique de Tab : l'ordre du DOM, pas du CSS

Quand tu appuies sur Tab, le navigateur déplace le focus d'un élément focusable au suivant. Règle d'or à graver : l'ordre de tabulation suit l'ordre du DOM, c'est-à-dire l'ordre des balises dans ton HTML. Pas l'ordre visuel, pas le CSS. Tu peux déplacer un bouton à droite avec float ou en haut avec order Flexbox : le Tab, lui, l'atteindra à sa position dans le code source.

Bonne nouvelle, rappel direct de la leçon 2 : les éléments natifs sont déjà focusables tout seuls. Un <a href>, un <button>, un <input>, un <select> entrent dans l'ordre de tabulation sans que tu fasses quoi que ce soit. C'est précisément la récompense d'avoir choisi la bonne balise. Une <div>, elle, n'est pas focusable : le Tab l'ignore.

Pour les cas particuliers, il existe l'attribut tabindex :

  • tabindex="0" : rend focusable un élément qui ne l'est pas naturellement, à sa place normale dans l'ordre du DOM. Utile quand tu fabriques un composant custom (rare, et seulement si tu ne peux pas utiliser une vraie balise).
  • tabindex="-1" : retire l'élément de l'ordre de tabulation, mais le laisse focusable par script (element.focus()). C'est ce qui permet, par exemple, de déplacer le focus vers un message d'erreur après une soumission.
  • tabindex="3" et toute valeur positive : à fuir.

Ne mets jamais de tabindex positif. Un tabindex="3" dit au navigateur « visite-moi en troisième, avant tout ce qui n'a pas de numéro ». Tu casses l'ordre naturel du DOM et tu crées un ordre parallèle, fragile : ajoute un champ et toute ta numérotation est à refaire. L'utilisateur clavier saute alors de façon imprévisible aux quatre coins de la page. La bonne approche est inverse : range tes balises dans le bon ordre dans le HTML, et laisse le 0 (ou rien) faire le travail.

Le crime : outline: none

Voici la faute la plus répandue de tout le web. Par défaut, le navigateur dessine un anneau autour de l'élément focusé : c'est le focus visible. Et quelque part, un jour, un designer l'a trouvé moche. Alors quelqu'un a écrit cette ligne :

*:focus { outline: none; }

Le rendu est plus « propre ». Sauf que tu viens de crever les yeux de l'utilisateur clavier. Réfléchis : il navigue au Tab, mais il n'a aucune souris pour pointer où il en est. L'anneau était sa seule indication de position. Sans lui, le focus existe toujours (Entrée fonctionne), mais il est invisible. La personne tabule à l'aveugle, sans jamais savoir quel bouton elle va activer. C'est exactement la troisième question du test de 30 secondes qui s'effondre : « sais-tu où tu es ? » Non.

Ne retire jamais le focus sans le remplacer. Si l'anneau par défaut te déplaît, redessine-le, ne le supprime pas : :focus { outline: 2px solid #2b6cb0; outline-offset: 2px; }. Le focus visible n'est pas négociable : c'est même un critère WCAG (2.4.7, niveau AA). Le supprimer, c'est livrer un site cassé pour une partie de tes utilisateurs.

Le compromis moderne : :focus-visible

Le vrai grief du designer était subtil : l'anneau apparaissait même au clic souris, ce qui faisait « cliqué/sélectionné » de façon inélégante. La plateforme a entendu et a créé une pseudo-classe qui réconcilie tout le monde : :focus-visible. Elle n'applique le style que lorsque le navigateur juge l'anneau utile, c'est-à-dire en pratique lors d'une navigation clavier, et pas lors d'un simple clic souris sur un bouton.

/* l'anneau au clavier, pas au clic souris */
:focus-visible {
  outline: 2px solid #2b6cb0;
  outline-offset: 2px;
}

C'est exactement le :focus croisé au cours CSS (leçon sur les sélecteurs et pseudo-classes), mais en plus fin : même idée d'« état au focus », appliqué seulement quand l'utilisateur en a besoin. Le designer a son rendu propre à la souris, l'utilisateur clavier garde son repère. Personne ne perd.

À toi : répare la page inaccessible

Cette page a deux fautes. D'abord, un outline: none global qui rend le focus invisible. Ensuite, une fausse « action » bâtie sur une <div> cliquable : la souris la déclenche, mais le Tab ne l'atteint même pas. Ta mission : passer en :focus-visible avec un anneau visible, et remplacer la div par un vrai <a href> (ou <button>), naturellement focusable. Clique Exécuter, puis vérifie au Tab.

Exercice : rends la page navigable au clavier

Remplace outline: none par un :focus-visible qui dessine un anneau, et transforme la <div> cliquable en vrai lien <a href> ou <button>.

Corrige le code
Voir une solution
<style>
  /* l'anneau au clavier, propre à la souris */
  :focus-visible {
    outline: 2px solid #f6ad55;
    outline-offset: 2px;
  }
  .lien { /* ...inchangé... */ }
</style>

<a href="#" class="lien">Vrai lien (focusable)</a>

<!-- une vraie action = un vrai bouton -->
<button class="lien" onclick="alert('clic')">Vrai bouton</button>

Au Tab, les deux éléments reçoivent maintenant un anneau orange, et le « bouton » est enfin atteignable. La règle tient en une phrase : une action s'écrit avec <button> ou <a>, jamais avec une <div>.

Le skip link : « Aller au contenu »

Imagine un site avec un menu de 20 liens en haut de chaque page. L'utilisateur souris l'ignore : il vise directement le contenu. Mais l'utilisateur clavier, lui, doit retraverser les 20 liens au Tab, sur chaque page, avant d'atteindre l'article. À la dixième page, c'est l'abandon.

La solution est un grand classique : le skip link, un lien « Aller au contenu » placé tout en premier dans le HTML. Il est invisible tant qu'on ne le focus pas, et il apparaît au premier Tab. L'activer saute par-dessus la navigation, droit au contenu principal.

<a href="#contenu" class="skip-link">Aller au contenu</a>

<style>
.skip-link {
  position: absolute;
  top: -60px;            /* hors de l'écran par défaut */
  left: 0;
  background: #2b6cb0;
  color: #fff;
  padding: 10px 16px;
}
.skip-link:focus-visible {
  top: 0;                /* réapparaît au focus clavier */
}
</style>

Le site que tu lis en a un. Le premier élément focusable de ce gabarit est exactement <a href="#lesson-content" class="skip-link">Aller au contenu</a>. Essaie : clique n'importe où dans la page, puis appuie une fois sur Tab. Un petit bandeau « Aller au contenu » apparaît en haut à gauche. C'est ce code-là, en vrai, sur cette page.

Ce que WCAG 2.2 ajoute

La version 2.2 des règles d'accessibilité (WCAG) a introduit deux critères qui touchent directement le clavier et le pointage. Tu vas les rencontrer en audit.

Deux critères WCAG 2.2 (niveau AA).

  • Taille de cible minimale 24×24 px (critère 2.5.8). Une cible cliquable (bouton, lien, case) doit faire au moins 24 par 24 pixels. Les cibles minuscules excluent les doigts épais, les mains qui tremblent, l'usage en déplacement. Donne de l'air à tes zones tactiles.
  • Focus non masqué (critère 2.4.11). Quand un élément reçoit le focus, il ne doit pas être caché par un autre. Le piège classique : un header en position: sticky qui recouvre l'élément focusé quand l'utilisateur tabule en bas de page. L'anneau existe, mais il est dessous, donc invisible.
Prédis avant de lire

Un menu déroulant s'ouvre uniquement au survol de la souris (:hover). Que se passe-t-il quand un utilisateur clavier arrive dessus au Tab ?

Voir la réponse

Rien : le sous-menu reste fermé, donc inatteignable. Le clavier ne déclenche pas :hover (il n'y a pas de curseur qui survole). L'utilisateur tabule jusqu'au libellé parent, puis... le contenu du menu ne s'ouvre jamais et ses liens sont hors de portée. Un menu accessible doit donc s'ouvrir aussi au focus (gérer le :focus/:focus-within, ou ouvrir/fermer au clavier en JS). Règle générale : tout ce qui dépend du seul :hover est invisible pour le clavier.

Le chemin du Tab sur une page

Voici l'ordre dans lequel le focus voyage, en suivant le DOM. Note le skip link : c'est le tout premier arrêt, le raccourci qui saute par-dessus la nav pour aller droit au contenu.

Maquette d'une page web avec le chemin de tabulation numéroté. Arrêt 0 : le skip link « Aller au contenu », tout en haut, invisible jusqu'au premier Tab. Arrêt 1 : le logo. Arrêt 2 : la navigation. Arrêt 3 : le contenu principal. Une flèche en pointillés part du skip link et saute directement au contenu principal, court-circuitant la navigation. Aller au contenu (skip link) 0 Logo 1 Navigation 2 Contenu principal liens et boutons de l'article 3 le skip link saute la nav
L'ordre du Tab suit le DOM (0 → 1 → 2 → 3). Le skip link offre un raccourci direct vers le contenu.

Put your mouse down. Now manage.

Who browses a site without a mouse? More people than you think. The person with a motor disability who can't aim a cursor. The blind person whose screen reader drives everything from the keyboard. The power user who Tabs three times faster. And you, the day your mouse dies mid-rush.

For all of them, the keyboard isn't a comfort: it's the only way in. In lesson 2 we saw that semantic HTML does 80% of the work. The keyboard is where that 80% is proven or collapses.

The 30-second test: on your page, let go of the mouse. Press Tab, again, again. Three questions, all the way: can you reach every link, button, field? Can you activate them (Enter, Space)? And above all: do you always know where you are? If the answer is no even once, a keyboard user is stuck. This test costs nothing and reveals almost everything.

How Tab works: DOM order, not CSS

When you press Tab, the browser moves focus from one focusable element to the next. Golden rule to carve in: tab order follows DOM order, meaning the order of tags in your HTML. Not the visual order, not the CSS. You can push a button to the right with float or to the top with Flexbox order: Tab will still reach it at its position in the source code.

Good news, a direct callback to lesson 2: native elements are already focusable on their own. An <a href>, a <button>, an <input>, a <select> enter the tab order without you doing anything. That's exactly the reward for picking the right tag. A <div>, on the other hand, is not focusable: Tab ignores it.

For special cases, there's the tabindex attribute:

  • tabindex="0" — makes focusable an element that isn't naturally, at its normal place in DOM order. Useful when you build a custom component (rare, and only if you can't use a real tag).
  • tabindex="-1" — removes the element from the tab order, but keeps it focusable by script (element.focus()). That's what lets you, for instance, move focus to an error message after a submit.
  • tabindex="3" and any positive value — to be avoided.

Never use a positive tabindex. A tabindex="3" tells the browser "visit me third, before anything without a number". You break the natural DOM order and create a fragile parallel order: add one field and your whole numbering must be redone. The keyboard user then jumps unpredictably across the page. The right approach is the opposite: order your tags correctly in the HTML and let 0 (or nothing) do the work.

The crime: outline: none

Here's the most widespread mistake on the whole web. By default, the browser draws a ring around the focused element: that's the visible focus. And somewhere, one day, a designer found it ugly. So someone wrote this line:

*:focus { outline: none; }

The look is "cleaner". Except you just blinded the keyboard user. Think about it: they navigate with Tab, but they have no mouse to point where they are. The ring was their only position indicator. Without it, focus still exists (Enter works), but it's invisible. The person Tabs blind, never knowing which button they're about to activate. That's exactly the third question of the 30-second test collapsing: "do you know where you are?" No.

Never remove focus without replacing it. If the default ring displeases you, redraw it, don't delete it: :focus { outline: 2px solid #2b6cb0; outline-offset: 2px; }. Visible focus is non-negotiable: it's even a WCAG criterion (2.4.7, level AA). Removing it ships a broken site for a portion of your users.

The modern compromise: :focus-visible

The designer's real grievance was subtle: the ring appeared even on a mouse click, making things look "clicked/selected" inelegantly. The platform listened and created a pseudo-class that reconciles everyone: :focus-visible. It applies the style only when the browser judges the ring useful — in practice during keyboard navigation, and not on a plain mouse click on a button.

/* the ring on keyboard, not on mouse click */
:focus-visible {
  outline: 2px solid #2b6cb0;
  outline-offset: 2px;
}

It's exactly the :focus you met in the CSS course (the lesson on selectors and pseudo-classes), but finer: same "focused state" idea, applied only when the user needs it. The designer gets a clean mouse look, the keyboard user keeps their landmark. Nobody loses.

Your turn: fix the inaccessible page

This page has two faults. First, a global outline: none that makes focus invisible. Then a fake "action" built on a clickable <div>: the mouse triggers it, but Tab never even reaches it. Your mission: switch to :focus-visible with a visible ring, and replace the div with a real <a href> (or <button>), naturally focusable. Click Run, then check with Tab.

Exercise: make the page keyboard-navigable

Replace outline: none with a :focus-visible that draws a ring, and turn the clickable <div> into a real <a href> link or a <button>.

Fix the code
Show a solution
<style>
  /* the ring on keyboard, clean on mouse */
  :focus-visible {
    outline: 2px solid #f6ad55;
    outline-offset: 2px;
  }
  .link { /* ...unchanged... */ }
</style>

<a href="#" class="link">Real link (focusable)</a>

<!-- a real action = a real button -->
<button class="link" onclick="alert('click')">Real button</button>

On Tab, both elements now get an orange ring, and the "button" is finally reachable. The rule fits in one sentence: an action is written with <button> or <a>, never with a <div>.

The skip link: "Skip to content"

Imagine a site with a 20-link menu at the top of every page. The mouse user ignores it: they aim straight at the content. But the keyboard user has to Tab back through all 20 links, on every page, before reaching the article. By the tenth page, they give up.

The fix is a classic: the skip link, a "Skip to content" link placed first in the HTML. It's invisible until focused, and it appears on the first Tab. Activating it jumps over the navigation, straight to the main content.

<a href="#content" class="skip-link">Skip to content</a>

<style>
.skip-link {
  position: absolute;
  top: -60px;            /* off-screen by default */
  left: 0;
  background: #2b6cb0;
  color: #fff;
  padding: 10px 16px;
}
.skip-link:focus-visible {
  top: 0;                /* reappears on keyboard focus */
}
</style>

The site you're reading has one. The first focusable element of this template is exactly <a href="#lesson-content" class="skip-link">Aller au contenu</a>. Try it: click anywhere on the page, then press Tab once. A small "Skip to content" banner appears at the top left. That's this very code, live on this page.

What WCAG 2.2 adds

Version 2.2 of the accessibility guidelines (WCAG) introduced two criteria that bear directly on keyboard and pointing. You'll meet them in audits.

Two WCAG 2.2 criteria (level AA):

  • Minimum target size 24×24 px (criterion 2.5.8). A clickable target (button, link, checkbox) must be at least 24 by 24 pixels. Tiny targets exclude thick fingers, trembling hands, on-the-go use. Give your touch zones some air.
  • Focus not obscured (criterion 2.4.11). When an element receives focus, it must not be hidden by another. The classic trap: a position: sticky header that covers the focused element when the user Tabs at the bottom of the page. The ring exists, but it's underneath, so invisible.
Predict before reading on

A dropdown menu opens only on mouse hover (:hover). What happens when a keyboard user reaches it with Tab?

Show the answer

Nothing: the submenu stays closed, hence unreachable. The keyboard doesn't trigger :hover (there's no cursor hovering). The user Tabs to the parent label, then... the menu content never opens and its links are out of reach. An accessible menu must therefore open on focus too (handle :focus/:focus-within, or open/close from the keyboard in JS). General rule: anything that depends on :hover alone is invisible to the keyboard.

The Tab path across a page

Here's the order in which focus travels, following the DOM. Note the skip link: it's the very first stop, the shortcut that jumps over the nav straight to the content.

Mockup of a web page with the numbered tab path. Stop 0: the "Skip to content" skip link, at the very top, invisible until the first Tab. Stop 1: the logo. Stop 2: the navigation. Stop 3: the main content. A dashed arrow starts from the skip link and jumps straight to the main content, short-circuiting the navigation. Skip to content (skip link) 0 Logo 1 Navigation 2 Main content the article's links and buttons 3 the skip link jumps the nav
Tab order follows the DOM (0 → 1 → 2 → 3). The skip link offers a direct shortcut to the content.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique à un collègue pourquoi outline: none est un problème d'accessibilité, et ce qu'il faut faire à la place.

Une bonne explication dit : l'anneau de focus est la seule indication de position de l'utilisateur clavier, qui n'a pas de curseur. outline: none le rend invisible : le focus existe encore (Entrée marche) mais on tabule à l'aveugle. La solution n'est jamais de supprimer, c'est de redessiner l'anneau, idéalement avec :focus-visible (anneau au clavier, propre au clic souris).
🧠 Rappel libre
Rappel libre

Sans remonter : quel ordre suit le Tab, et que valent tabindex="0" vs tabindex="-1" ?

Le Tab suit l'ordre du DOM (l'ordre des balises dans le HTML), pas l'ordre visuel ni le CSS. tabindex="0" rend un élément focusable à sa place normale dans cet ordre. tabindex="-1" le retire de l'ordre du Tab mais le laisse focusable par script (.focus()). Et toute valeur positive (tabindex="3") est à proscrire : elle casse l'ordre naturel.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Tu demandes à l'IA de « rendre le menu plus propre et de mettre le bouton d'inscription en avant dans la tabulation ». Elle répond : « Fait ! J'ai ajouté *:focus { outline: none; } pour un rendu net, et tabindex="1" sur le bouton CTA pour qu'il soit prioritaire au Tab. » Tu acceptes, ou tu rejettes ?

À rejeter : double faute. (1) *:focus { outline: none; } supprime le focus visible partout : l'utilisateur clavier tabule à l'aveugle, c'est l'erreur d'accessibilité numéro un. (2) tabindex="1" casse l'ordre naturel du DOM : le bouton sera visité avant tout le reste de façon imprévisible, et la moindre modif du formulaire fera dérailler la numérotation. Les deux corrections : un :focus-visible qui redessine l'anneau (au lieu de le supprimer), et aucun tabindex positif : pour mettre le CTA « en avant », on le place plus tôt dans le HTML, pas avec un numéro.
Tu déplaces un bouton tout en haut visuellement avec order en Flexbox, mais il reste écrit en dernier dans le HTML. Dans quel ordre le Tab l'atteindra-t-il ?
Un designer trouve l'anneau de focus moche même au clic souris. Quelle solution garde l'accessibilité ?
À quoi sert le skip link placé en premier dans le HTML ?
Pourquoi un tabindex="3" (valeur positive) est-il à proscrire ?
Prochaine étape

Tu navigues sans souris. Maintenant, vois sans écran : leçon 4, le texte alternatif, et l'art de décrire la fonction plutôt que l'image.

Leçon 4 : Le texte alternatif →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement