RGAA Lab

Composants accessibles

La modale

Une fenêtre qui interrompt tout doit rendre ce qu'elle prend : le focus, le clavier, l'annonce au lecteur d'écran. L'élément natif <dialog> donne les quatre. Démo, code, critères RGAA.

5 critères RGAA en jeu

Essayez-la

Retirer la page de l'audit ?

Ses vérifications déjà tranchées seront conservées, mais la page ne comptera plus dans le taux de conformité.

Ouvrez-la, puis n’utilisez que le clavier : Tab circule entre les deux boutons sans jamais sortir de la fenêtre, Échap ferme et rend le focus au bouton d’ouverture. C’est tout le contrat d’une modale, et il tient en une phrase : ce qu’elle prend, elle le rend.

Cette démo existe aussi en page autonome — ouvrez-la avec notre extension pour vérifier ce qu’elle déclare.

L’erreur courante

La modale bricolée, c’est deux div : un voile sombre, une boîte par-dessus, un peu de JavaScript pour les afficher.

<!-- À ne pas faire : rien ici ne dit « dialogue » à personne -->
<div class="voile">
	<div class="boite">
		<h2>Retirer la page de l'audit ?</h2></div>
</div>

Visuellement, tout y est. Au clavier et au lecteur d’écran, rien n’y est :

  • Tab continue de circuler dans la page derrière le voile — on navigue dans un contenu qu'on ne voit plus ;
  • Échap ne ferme rien ;
  • le lecteur d’écran n’annonce pas qu’un dialogue s’est ouvert, ni son titre ;
  • à la fermeture, le focus reste perdu là où il était.

Chacun de ces quatre manques se répare à la main — piège de focus, écouteur clavier, role="dialog", mémorisation de l’élément déclencheur — et chaque réparation est un endroit où se tromper. Le HTML a un élément qui donne les quatre d’un coup.

Le code, pas à pas

1. L’élément <dialog> et showModal()

<button type="button" data-ouvre="confirmation">
	Retirer la page de l'audit
</button>

<dialog id="confirmation">
	<h2>Retirer la page de l'audit ?</h2>
	<p>
		Ses vérifications déjà tranchées seront conservées, mais la page ne
		comptera plus dans le taux de conformité.
	</p>
</dialog>
const dialogue = document.getElementById('confirmation');
document
	.querySelector('[data-ouvre="confirmation"]')
	.addEventListener('click', () => dialogue.showModal());

C’est showModal() — pas show() — qui fait la modale. Le navigateur prend tout en charge : le focus entre dans la fenêtre et n’en sort plus, Échap ferme, le reste de la page devient inerte — invisible aux technologies d’assistance et inatteignable au clavier — et à la fermeture, le focus revient à l’élément qui a ouvert. Les quatre manques de la version bricolée, réglés par un appel de fonction.

L’élément est disponible partout depuis 2022 ; il n’y a plus de version de navigateur à attendre ni de solution de repli à écrire.

2. Le nom : ce que le lecteur d’écran annonce

À l’ouverture, un lecteur d’écran annonce le rôle — « dialogue » — et le nom de la fenêtre. Sans nom, l’utilisateur sait qu’un dialogue s’est ouvert, mais pas lequel. Le titre est déjà là : on le relie.

<dialog id="confirmation" aria-labelledby="titre-confirmation">
	<h2 id="titre-confirmation">Retirer la page de l'audit ?</h2></dialog>

3. Le focus initial : sur l’action la moins risquée

Par défaut, le focus se pose sur le premier élément focalisable de la fenêtre. Dans un dialogue de confirmation, on choisit où il se pose : sur l’action qui n’engage à rien. autofocus est fait pour ça — dans un <dialog>, il ne s’applique qu’à l’ouverture, pas au chargement de la page.

<form method="dialog">
	<button value="annuler" autofocus>Annuler</button>
	<button value="confirmer">Retirer la page</button>
</form>

4. La fermeture : un formulaire, pas un écouteur

method="dialog" ferme la fenêtre à l’activation de n’importe quel bouton du formulaire, et dépose la value du bouton choisi dans dialogue.returnValue. Pas d’écouteur de clic par bouton : un seul événement close, où qu’on lise la décision.

dialogue.addEventListener('close', () => {
	if (dialogue.returnValue === 'confirmer') {
		// retirer la page
	}
});

Le retour du focus au bouton d’ouverture, lui, est automatique — quelle que soit la façon dont la fenêtre s’est fermée.

Au clavier

ToucheEffet
TabPasse à l’élément suivant dans la modale ; après le dernier, revient au premier.
Maj + TabMême circulation, en arrière.
ÉchapFerme la modale ; le focus revient au bouton qui l’a ouverte.
Entrée / EspaceActive le bouton qui a le focus ; via method="dialog", ferme la fenêtre.

Ce que le lecteur d’écran restitue

À l’ouverture, avec le code ci-dessus, un lecteur d’écran annonce trois choses : le nom de la fenêtre, son rôle, et l’élément qui reçoit le focus — de l’ordre de « Retirer la page de l’audit ?, dialogue — Annuler, bouton ». La formulation exacte varie d’un lecteur à l’autre ; les trois informations, non. Si l’une manque à l’appel — le rôle absent, un nom vide — c’est le balisage qu’il faut regarder, pas le lecteur.

Pendant que la fenêtre est ouverte, la navigation du lecteur d’écran est elle aussi confinée à la modale : l’arrière-plan inerte n’existe plus pour personne, et c’est le comportement attendu.

Checklist

  • Utilisez <dialog> et showModal() — pas deux div et du JavaScript.
  • Donnez un nom à la fenêtre : aria-labelledby vers son titre.
  • Posez le focus initial sur l’action la moins risquée, avec autofocus.
  • Fermez par method="dialog" ; ne supprimez jamais la fermeture par Échap.
  • Vérifiez le retour du focus : fenêtre fermée, il est sur le bouton d’ouverture.

Les critères RGAA en jeu

Vérifiez votre composant avec l'extension

Pour aller plus loin

Sources