Aller au contenu principal
Accessio

SPA et lecteur d'écran : annonce de route, lang et statut

Une SPA qui se rafraîchit sans navigation de page expose des bugs d'accessibilité invisibles au test au clavier : les utilisateurs de lecteur d'écran n'entendent pas les changements de route, les nouveaux panneaux apparaissent sans annonce, l'attribut lang ne se met pas à jour. Les critères WCAG 4.1.3 (Status Messages), 2.4.8 (Location), 3.1.1 (Language of Page) deviennent des points critiques à piloter en JavaScript.

Le bug classique : changement de route silencieux

Dans une SPA, l'utilisateur clique sur un lien qui appelle un router interne : push(), replace(), navigate(). L'URL change, l'historique s'actualise, mais l'utilisateur de lecteur d'écran reste sur la page précédente sans entendre d'annonce. C'est un échec aux critères WCAG 4.1.3 (Status Messages, niveau AA) et 2.4.8 (Location, niveau AAA). Testez en activant VoiceOver : si vous ne comprenez pas où vous êtes, le bug est présent.

aria-live pour annoncer la navigation

Pour résoudre le silence de route, ajoutez une région aria-live=polite dans le DOM, idéalement persistante, hors du contenu changeant. À chaque changement de route, écrivez le titre de la nouvelle page : Page chargée : Tableau de bord. Évitez aria-live=assertive : cette valeur interrompt la lecture en cours, ce qui devient intrusif.

Mise à jour de l'attribut lang dans une SPA multilingue

Pour une SPA multilingue, le critère WCAG 3.1.1 (niveau A) exige que la langue principale soit déclarée via lang sur l'élément html. Le changement de route ne recharge pas le document, donc document.documentElement.lang doit être mis à jour programmatiquement à chaque navigation linguistique. En Next.js, le metadata API permet d'exporter lang dans chaque layout.

Focus management entre deux routes

Quand la route change, le focus doit être repositionné : souvent sur le H1 de la nouvelle page (focus naturel), ou sur la zone principale identifiée par main#contenu. Sans repositionnement, l'utilisateur reste dans le contexte de la page précédente, parfois dans une zone qui n'existe plus. Le critère WCAG 2.4.3 (Focus Order, niveau A) couvre partiellement cet aspect.

Headings de page : annoncer le contexte

Chaque page d'une SPA doit avoir un H1 unique qui décrit son sujet. Le critère WCAG 1.3.1 impose une structure Hn correcte, et 2.4.6 (Headings and Labels, niveau AA) exige que les Hn soient pertinents. Pour une SPA dont le contenu change selon la route, régénérez dynamiquement le H1 : c'est l'annonce naturelle que les utilisateurs de lecteur d'écran entendent en début de page.

Bibliothèques et frameworks : Next.js, Nuxt, SvelteKit

Chaque méta-framework a ses conventions : Next.js App Router gère l'attribut lang via metadata API, mais pour les annonces de route il faut un hook custom. Nuxt 3 expose useRoute() et son i18n, mais les annonces aria-live restent à implémenter. SvelteKit fait la mise à jour du focus naturellement, mais l'attribut lang demande un plugin ou layout. Trois invariants à votre charge : région aria-live persistante, H1 dynamique, synchronization document.documentElement.lang.

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 gratuit

Questions fréquentes

aria-live : polite ou assertive ?
Préférez polite pour la majorité des annonces (routes, charge de contenu, filtrage) : le lecteur d'écran attend une pause de l'utilisateur. Réservez assertive aux messages critiques bloquants (panne serveur, déconnexion). Une région unique live=polite dans le layout principal suffit pour l'essentiel des cas SPA.
Comment annoncer un changement de page ?
Placez une région persistante (souvent un div screen-reader-only) avec aria-live=polite dans le layout racine. À chaque changement de route, écrivez le titre de la nouvelle page : Page chargée : Tableau de bord. Cette annonce seule couvre WCAG 4.1.3 pour la majorité des cas.
Next.js App Router : annonces de route par défaut ?
Non, Next.js App Router ne fournit pas d'annonce par défaut. C'est un choix de design pour éviter l'intrusion, mais cela expose un bug d'accessibilité. Vous devez implémenter la région aria-live + le hook de mise à jour : useEffect sur pathname() + setState sur la région live.
Headings de page : faut-il changer le H1 ?
Oui, le H1 doit refléter le sujet de la page courante. Pour une SPA avec routeur interne : à chaque navigation, mettre à jour le H1 avec le titre de la nouvelle vue. C'est ce que les utilisateurs de lecteur d'écran entendent en début de page, et cela sert d'annonce naturelle du changement de contexte.
Langue : qui met à jour l'attribut lang ?
Pour Next.js, le metadata API permet d'exporter lang dans chaque layout : export const metadata = { other: { 'html-attrs': { lang: 'fr' } } }. Pour les routes client-side, un useLayoutEffect met à jour document.documentElement.lang. Le critère WCAG 3.1.1 niveau A est strict : sans attribut lang correct, c'est un échec qui contrevient à l'EAA.
L'EAA privé impose-t-elle des annonces vocales ?
Depuis le 28 juin 2025, l'EAA impose WCAG 2.1 AA aux services numériques privés. Le critère 4.1.3 (Status Messages, niveau AA) couvre les annonces de route. Une SPA sans région aria-live peut échouer. La DGCCRF peut sanctionner de 7 500 € à 15 000 €.
Pourquoi mon lecteur d'écran ne dit rien après navigation ?
Trois cas : (1) votre région aria-live est absente ou mal dimensionnée. (2) vous écrivez dans la région live après le premier render, mais le lecteur n'a pas eu le temps de l'observer : utilisez setTimeout minimal ou useLayoutEffect. (3) vous n'avez annoncé que des changements internes (URL) sans contenu textuel. Solution : annonce explicite + H1 dynamique.

Pour aller plus loin

Besoin d'aller au-delà du diagnostic ? Demandez un audit d'accessibilité ou consultez nos tarifs.

Retour à tous les guides d'accessibilité.