Focus piégé en React et Next.js : dialog, drawer, combobox
Le focus management reste le parent pauvre de l'accessibilité React. Tant que vos composants sont des div sur mesure, c'est votre code qui doit assurer le piégeage du focus dans une modale, son retour à la fermeture, et la cohérence avec la navigation lecteur d'écran. Critique pour WCAG 2.1.2, 2.4.3, 4.1.2 et amplifié par Next.js App Router où l'hydratation peut retarder le focus initial.
Trois scénarios critiques : modal, drawer, combobox
Les trois composants qui piègent ou déplacent le focus : (1) la modale (role=dialog) doit confiner le focus entre ses frontières et le restaurer à l'élément déclencheur. (2) le drawer suit la même règle mais accepte parfois un focus partagé avec le contenu principal. (3) la combobox et ses variantes (autocomplete, listbox) doivent gérer le focus sur l'input et la liste d'options avec les flèches. Chaque composant a un pattern WAI-ARIA Authoring Practices officiel à appliquer.
Piégeage du focus : pattern WAI-ARIA
Pour piéger le focus dans une modale, capturez les éléments focusables internes à l'ouverture, déplacez le focus sur le premier (souvent le bouton de fermeture ou le titre), puis écoutez Tab et Maj+Tab : si l'utilisateur quitte les bornes, ramenez le focus au début ou à la fin selon la direction. Ce piégeage respecte WCAG 2.1.2 niveau A. La touche Échap doit aussi fermer la modale.
Retour du focus : la règle element-that-opened
Quand la modale se ferme, le focus DOIT revenir sur l'élément qui l'a ouverte (règle element-that-opened), indispensable pour les utilisateurs clavier et lecteurs d'écran. Sans cela, ils reviennent à un endroit inattendu (souvent le body). Conservez une référence au déclencheur, puis appelez .focus() à la fermeture. Les bibliothèques sérieuses (react-aria, Radix UI, Headless UI) gèrent cela automatiquement.
react-focus-lock vs Headless UI vs react-aria
Trois options principales : (1) react-focus-lock, la plus utilisée, gérant le retour automatique. (2) Headless UI de Tailwind Labs (Dialog, Listbox, Combobox), prêt à l'emploi et CSS-friendly. (3) react-aria d'Adobe Spectrum, plus complet mais plus lourd à apprendre. Pour les cas simples, useEffect + addEventListener Tab suffisent. Pour les cas complexes, utilisez une bibliothèque reconnue.
Next.js App Router : hydratation et focus initial
Dans Next.js App Router, le rendu serveur (RSC) retarde parfois le focus initial : un composant interactif peut apparaître après l'hydratation, et un autofocus peut sauter après le streaming. Pour éviter cela : useEffect ou useLayoutEffect pour placer le focus après l'hydratation. Pour les routes, Next.js gère la navigation mais pas l'attribut lang ni les annonces aria-live : à votre charge. WCAG 4.1.3 impose les annonces de route.
Tester votre implémentation au lecteur d'écran
Trois tests : (1) Navigation au clavier seul : Tab doit piéger le focus dans la modale, Échap doit la fermer. (2) NVDA + Firefox : la modale est annoncée avec son rôle et son titre. (3) VoiceOver + Safari : après fermeture, retour effectif du focus au bouton déclencheur. Un test humain reste indispensable.
Testez l’accessibilité de votre site, gratuitement
Voyez en moins d'une minute où votre site rencontre ce type d'écart, gratuitement.
Lancer le diagnostic gratuitQuestions fréquentes
- Pourquoi mon focus saute après l'ouverture d'une modale ?
- Trois causes fréquentes : (1) focus placé sur un élément masqué visuellement mais rendu après l'animation. (2) oubli de preventDefault lors de la capture du Tab. (3) Next.js a streamé le composant après l'ouverture. Solution : useEffect après le paint complet.
- Modale accessible sans bibliothèque externe : faisable ?
- Oui, mais plus long. Vous devez gérer le focus trap manuellement, le retour du focus à l'élément déclencheur, la touche Échap, l'annonce du titre (aria-labelledby), le verrou de l'arrière-plan (inert ou aria-hidden). Comptez 50 à 100 lignes. Pour usages critiques, préférez react-aria ou Radix.
- react-focus-lock : taux d'adoption ?
- Très bonne, bibliothèque dominante dans React pour le focus trap. Utilisée par Material UI, react-modal. Pour des usages sans composants prêts à l'emploi, c'est l'option par défaut. Testez avec NVDA + Firefox, certaines versions anciennes avaient des bugs.
- Combobox accessible : quel pattern ?
- WAI-ARIA Authoring Practices : role=combobox sur l'input, role=listbox sur la liste, aria-activedescendant pour annoncer l'option courante. Flèches naviguent, Entrée sélectionne. Pour autocomplete, ajout aria-autocomplete=list ou equal. Pour virtualization, role=option sur chaque ligne visible et key stable.
- Next.js App Router piège le focus après navigation ?
- Pas nativement, mais des cas concrets piègent : un input en autofocus après Server Component peut sauter après l'hydratation. Une navigation client-side (next/link) ne déplace pas le focus. Pour résoudre : focus déplacé programmatiquement sur le H1 de la nouvelle page.
- WCAG 2.1.2 No Keyboard Trap : à surveiller ?
- Oui, critère niveau A testé en audit. Il détecte les composants où l'utilisateur ne peut pas sortir au clavier. Vérifiez : Échap ferme la modale, Tab sort correctement, Maj+Tab revient.
Pour aller plus loin
- Mise en conformité : les 6 étapes
- Vos obligations EAA (entreprises privées)
- Critère WCAG 2.4.7 : garantir un focus visible pour tous
- Formulaires multilingues accessibles : RTL, lang, pluriels
- SPA et lecteur d'écran : annonce de route, lang et statut
Besoin d'aller au-delà du diagnostic ? Demandez un audit d'accessibilité ou consultez nos tarifs.
Retour à tous les guides d'accessibilité.