Chapitre 6.4 — Les live regions
⏱️ TL;DR — Une live region est une zone dont le lecteur d’écran annonce le changement de contenu sans déplacer le focus. C’est la réponse au critère WCAG 4.1.3 (Messages de statut) : prévenir « Cours enregistré », « 12 résultats », « Erreur : e-mail invalide » sans interrompre la navigation. On la déclare avec
aria-live="polite"(attend une pause) ou"assertive"(coupe la parole), ou via les rôles raccourcisrole="status"(= polite) etrole="alert"(= assertive). Le piège n°1 : insérer la région au moment du message — trop tard, rien n’est annoncé.
🎯 Objectifs
- Comprendre à quoi sert une live region et le lien avec le critère 4.1.3.
- Choisir entre
aria-live="polite"et"assertive",role="status"etrole="alert". - Régler la granularité avec
aria-atomicetaria-relevant. - Éviter les deux pièges mortels : région insérée trop tard, contenu changé trop vite.
- Écrire un composant « annonceur » réutilisable.
Le problème : annoncer sans bouger le focus
Beaucoup de retours d’interface sont visuels : un toast « Enregistré ✓ », un compteur « 12 résultats », un spinner de chargement. Un utilisateur de lecteur d’écran, lui, ne voit pas ces changements — et il est peut-être en train de lire ailleurs. Deux mauvaises réponses existent : ne rien annoncer (il rate l’info), ou déplacer le focus sur le message (on lui coupe sa lecture et on le perd). La bonne réponse, c’est la live region : le lecteur d’écran surveille la zone et lit la mise à jour à voix haute tout en laissant le focus où il est.
C’est exactement l’objet du critère WCAG 4.1.3 (Messages de statut) : un message de statut doit être exposé aux technologies d’assistance sans recevoir le focus.
polite vs assertive
Le comportement se règle avec aria-live :
aria-live="polite"— le lecteur d’écran attend une pause dans son élocution avant d’annoncer. Pour tout ce qui n’est pas urgent : confirmation, compteur, statut de chargement. C’est le défaut à privilégier.aria-live="assertive"— le lecteur d’écran interrompt ce qu’il dit pour annoncer immédiatement. Réservé à l’urgent : erreur bloquante, perte de connexion, délai qui expire. À utiliser avec parcimonie — couper la parole est agressif.
<!-- Statut discret : annoncé dès que le lecteur d'écran a une pause -->
<div aria-live="polite">12 résultats</div>
<!-- Urgence : interrompt l'élocution en cours -->
<div aria-live="assertive">Session expirée, reconnecte-toi.</div>Les rôles raccourcis : status et alert
Deux rôles encapsulent les réglages les plus courants — pratiques car un role est plus difficile à oublier qu’un attribut :
role="status"équivaut àaria-live="polite"(+aria-atomic="true"). Pour les confirmations et statuts.role="alert"équivaut àaria-live="assertive"(+aria-atomic="true"). Pour les erreurs et alertes.
<!-- ✅ Confirmation discrète -->
<div role="status">Cours enregistré.</div>
<!-- ✅ Erreur importante -->
<div role="alert">Le paiement a échoué.</div>aria-atomic et aria-relevant
Deux réglages fins de ce que le lecteur d’écran relit :
aria-atomic="true"— relit toute la région à chaque changement, pas seulement le mot modifié. Utile pour « 3 notifications » → « 4 notifications » : sans lui, le lecteur pourrait n’annoncer que « 4 » hors contexte.aria-relevant— précise quels types de changements comptent :additions,removals,text,all. Par défautadditions text(ajouts et modifications de texte). On y touche rarement.
Les deux pièges mortels
Piège 1 — la région insérée trop tard
C’est **l’**erreur classique. Le lecteur d’écran ne surveille une région que s’il l’a repérée dans le DOM avant que son contenu change. Si tu crées la <div role="alert"> et son message dans le même rendu, beaucoup de lecteurs d’écran n’annoncent rien.
// ❌ La région n'existe qu'AVEC le message : souvent muet
{erreur && <div role="alert">{erreur}</div>}// ✅ La région existe TOUJOURS, on n'y injecte que le texte
<div role="alert">{erreur}</div>La zone doit être présente et vide dès le chargement ; tu ne fais qu’y écrire le texte ensuite.
Piège 2 — le contenu changé trop vite
Si tu réécris la région plusieurs fois en quelques millisecondes (frappe au clavier, requêtes qui se succèdent), le lecteur d’écran écrase les annonces et n’en lit qu’une, voire aucune. Il faut temporiser (debounce) les mises à jour et éviter de spammer la région.
⚠️ Piège — Monter la live region en même temps que son message.
{msg && <div role="status">{msg}</div>}a l’air propre mais reste muet : la région apparaît déjà remplie, le lecteur d’écran n’a rien vu « changer ». Garde le conteneur monté en permanence et n’injecte que le texte — c’est le changement de contenu d’une région existante qui déclenche l’annonce.
Un composant « annonceur » réutilisable
Plutôt que d’éparpiller des live regions, centralise-les dans une région montée en permanence, alimentée par un contexte. En React :
import { createContext, useContext, useState, useCallback } from "react";
const AnnonceContext = createContext(() => {});
export const useAnnonce = () => useContext(AnnonceContext);
export function AnnonceProvider({ children }) {
const [message, setMessage] = useState("");
const annoncer = useCallback((texte) => {
// On vide d'abord pour forcer un vrai « changement » même si
// le texte est identique, puis on réinjecte au tick suivant.
setMessage("");
requestAnimationFrame(() => setMessage(texte));
}, []);
return (
<AnnonceContext.Provider value={annoncer}>
{children}
{/* Région TOUJOURS montée, visuellement masquée mais lue */}
<div
role="status"
aria-live="polite"
aria-atomic="true"
className="sr-only"
>
{message}
</div>
</AnnonceContext.Provider>
);
}Utilisation depuis n’importe quel composant : const annoncer = useAnnonce(); annoncer("Cours enregistré.");. La région vit dans le layout, elle est déjà là quand le message arrive — les deux pièges sont désamorcés.
💡 Réflexe — La classe
sr-only(screen-reader-only) masque la région à l’écran tout en la gardant dans l’arbre d’accessibilité. N’utilise jamaisdisplay:nonenihiddenpour ça : ils retirent l’élément de l’arbre, et plus rien n’est annoncé. Le bon masquage combineposition:absolute, dimensions minimes etclip— jamais une disparition totale.
🧭 Sur A11yLearn — Quand l’apprenant termine un module, A11yLearn affichait un toast vert « Cours enregistré » que les utilisateurs de lecteur d’écran rataient totalement. On a branché le
AnnonceProviderdans le layout : désormaisannoncer("Cours enregistré.")fait lire la confirmation enpolite, sans déplacer le focus — l’apprenant reste sur le bouton « Suivant » et entend juste la confirmation glisser entre deux phrases. Zéro focus volé, information reçue.
📚 Aller plus loin — Le critère de référence est WCAG 4.1.3 (Messages de statut), niveau AA depuis WCAG 2.1. La spec WAI-ARIA décrit les live region attributes (
aria-live,aria-atomic,aria-relevant,aria-busy). Le support réel varie selon le couple navigateur/lecteur d’écran : teste toujours tes annonces avec NVDA et VoiceOver — voir Partie 5.
✏️ Exercices
Exercice 1 — Pourquoi c’est muet ? Ce code n’annonce jamais l’erreur. Corrige-le.
function Champ({ erreur }) {
return (
<div>
<input type="email" />
{erreur && <p role="alert">{erreur}</p>}
</div>
);
}✅ Solution
La live region n’existe qu’avec le message : elle apparaît déjà remplie, le lecteur d’écran ne voit rien « changer ». On garde le conteneur monté en permanence et on n’y injecte que le texte :
function Champ({ erreur }) {
return (
<div>
<input type="email" aria-invalid={Boolean(erreur)} />
<p role="alert">{erreur}</p>
</div>
);
}La balise <p role="alert"> est toujours présente (vide quand erreur est vide) ; c’est l’injection du texte dans une région existante qui déclenche l’annonce.
Exercice 2 — polite ou assertive ? Classe : (a) « 8 résultats trouvés », (b) « Connexion perdue », (c) « Brouillon enregistré », (d) « Le fichier dépasse 5 Mo, envoi refusé ».
✅ Solution
Polite (role="status") : (a) compteur de résultats, (c) confirmation d’enregistrement — rien d’urgent, on attend une pause.
Assertive (role="alert") : (b) perte de connexion, (d) erreur bloquante d’envoi — il faut interrompre pour prévenir tout de suite.
Règle : assertive seulement quand couper la parole se justifie. Dans le doute, polite.
🧠 Quiz de révision
1. À quoi sert une live region ?
À faire annoncer un changement de contenu par le lecteur d’écran sans déplacer le focus. Elle répond au critère WCAG 4.1.3 (Messages de statut) : prévenir d’une confirmation, d’un compteur ou d’une erreur sans interrompre la navigation de l’utilisateur.
2. Différence entre aria-live="polite" et "assertive" ?
aria-live="polite" et "assertive" ?polite attend une pause dans l’élocution avant d’annoncer (défaut, pour le non-urgent). assertive interrompt immédiatement (réservé à l’urgent : erreur bloquante, perte de connexion). On privilégie toujours polite.
3. Que valent role="status" et role="alert" ?
role="status" et role="alert" ?role="status" = aria-live="polite" (+ aria-atomic="true") pour les confirmations. role="alert" = aria-live="assertive" (+ aria-atomic="true") pour les erreurs. Ce sont des raccourcis pratiques, plus durs à oublier qu’un attribut.
4. Quel est le piège de la « région insérée trop tard » ?
Si tu crées la région en même temps que son message ({msg && <div role="alert">{msg}</div>}), le lecteur d’écran ne l’a pas repérée « avant » et n’annonce rien. La région doit être montée en permanence et vide ; on n’y injecte que le texte ensuite.
5. Pourquoi ne pas masquer une live region avec display:none ?
display:none ?Parce que display:none (comme hidden) retire l’élément de l’arbre d’accessibilité : plus rien n’est annoncé. Pour masquer visuellement tout en gardant l’annonce, on utilise une classe sr-only (position absolue, clip), jamais une disparition totale.
Chapitre suivant : Les erreurs fréquentes — le catalogue des abus ARIA et leurs correctifs.