Le contenu au survol et au focus
Une infobulle, un aperçu, un sous-menu : tout ce qui apparaît au survol doit apparaître aussi au focus, se laisser survoler sans fuir, et se refermer par Échap. Démo, code, critères RGAA.
4 critères RGAA en jeu
Essayez-le
Un audit porte sur cent six critères, et son Le rapport des critères conformes au nombre de critères applicables. Les critères non applicables sont exclus du calcul. se lit en pourcentage.
Le contenu qui apparaît au survol relève du Contenus additionnels au survol ou au focus Ils doivent pouvoir être survolés, refermés sans déplacer le pointeur, et rester affichés tant qu'on ne les a pas quittés. Lire la fiche du critère.
- Infobulle : masqué
- Aperçu : masqué
Survolez un déclencheur, puis descendez lentement la souris sur le contenu qui vient d’apparaître : il reste. Refaites-le au clavier — Tab jusqu’au déclencheur, le contenu s’affiche ; Tab encore, et vous entrez dans l’aperçu pour atteindre son lien. Puis le geste décisif : survolez un déclencheur sans rien toucher au clavier, et appuyez sur Échap — le contenu se retire alors que le pointeur n’a pas bougé, et la ligne d’état passe à « refermé par Échap ». Elle dit à chaque instant pourquoi le contenu reste ou non affiché.
Cette démo existe aussi en page autonome.
L’erreur courante
L’infobulle en CSS pur : un span masqué, révélé par le :hover de son parent.
<!-- À ne pas faire -->
<span class="astuce">
taux de conformité
<span class="bulle">Le rapport des critères conformes aux critères applicables.</span>
</span> /* À ne pas faire : le survol est la seule porte d'entrée */
.astuce .bulle {
display: none;
}
.astuce:hover .bulle {
display: block;
} Cette bulle n’existe que pour qui a une souris. Au clavier, rien ne se passe :
le span déclencheur n’est pas focalisable, et lui ajouter :focus n’y changera
rien — un span ne reçoit jamais le focus. Sur un écran tactile, il n’y a pas
de survol du tout. Et l’explication perdue est presque toujours celle dont on
avait le plus besoin, puisque c’est pour elle qu’on a écrit l’infobulle.
Le deuxième défaut est plus discret : même à la souris, la bulle disparaît dès que le pointeur la quitte. Impossible d’aller la lire à la loupe, d’en copier le texte, ou d’y cliquer si elle contient un lien.
Trois variantes du même problème :
- Le sous-menu séparé de son libellé par un vide. Le pointeur descend, sort du libellé avant d’atteindre le panneau, et tout se referme sous lui. La rubrique reste inaccessible tant qu’on ne devine pas le trajet exact.
- L’attribut
title. Il ne s’affiche pas à la prise de focus clavier dans les navigateurs courants, ni au doigt, et son délai d’apparition ne se règle pas. Le référentiel le range en cas particulier du critère 10.13 — c’est le navigateur qui l’affiche, pas vous — ce qui le met hors du champ du critère sans le rendre recommandable pour autant. - La bulle qui se retire au bout de trois secondes. Elle disparaît pendant qu’on la lit, et c’est exactement ce que la troisième condition du critère 10.13 interdit.
Le code, pas à pas
1. Un déclencheur qui peut recevoir le focus
<button type="button" aria-describedby="bulle-taux">taux de conformité</button> Tout part de là. Un contenu additionnel s’accroche à un <button> ou à un <a href> — quelque chose qui prend le focus par nature. C’est le seul moyen
pour que le clavier ait un endroit où arriver.
Si le déclencheur doit rester du texte au fil d’une phrase, c’est le bouton
qu’on habille : bordure pointillée, cursor: help, pas de fond. Ce qu’il ne
faut pas faire, c’est poser tabindex="0" sur un span pour se dispenser du
bouton — on obtiendrait un élément focalisable qui ne s’annonce comme rien.
2. Un groupe qui enveloppe le déclencheur et le contenu
<span class="groupe">
<button type="button" aria-describedby="bulle-taux">taux de conformité</button>
<span id="bulle-taux" role="tooltip" class="bulle">Le rapport des critères…</span>
</span> .groupe .bulle {
display: none;
}
.groupe:hover .bulle,
.groupe:focus-within .bulle {
display: block;
} Deux conditions du critère 10.13 se règlent en une seule ligne de CSS.
:hover sur le groupe, et non sur le déclencheur seul : le contenu est un
descendant du groupe, donc amener le pointeur dessus ne fait pas sortir du
groupe. C’est ce qui le rend survolable.
:focus-within fait le même travail pour le focus, et couvre le cas où le
contenu renferme lui-même un lien : le focus est alors dans le groupe, le
contenu reste affiché.
3. Le pont : le décalage appartient au contenu
.bulle {
position: absolute;
top: 100%;
/* Transparent, mais à l'intérieur du groupe : le pointeur ne traverse rien. */
padding-top: 0.5rem;
} C’est le remède au sous-menu qui se ferme en chemin. Si l’écart entre le
déclencheur et le panneau est une margin, il est hors de tout élément : le
pointeur qui le traverse a quitté le groupe, et le panneau se referme. Le même
écart écrit en padding transparent sur le contenu appartient au groupe, et
le trajet devient continu.
La règle générale tient en une phrase : la zone survolable doit être d’un seul tenant, du déclencheur jusqu’au contenu.
4. Échap, en JavaScript
// Sur le document, pas sur le groupe : quand le contenu n'a été ouvert qu'au
// survol, le focus est ailleurs dans la page et le groupe ne verrait rien.
document.addEventListener('keydown', (evenement) => {
if (evenement.key !== 'Escape' || !estAffiche()) return;
groupe.classList.add('renvoye');
// Le focus était dans le contenu qu'on vient de masquer : il n'a plus d'hôte.
if (contenu.contains(document.activeElement)) declencheur.focus();
}); Le CSS sait afficher, il ne sait pas renoncer. La première condition du critère 10.13 — pouvoir masquer le contenu sans déplacer le focus ni le pointeur — demande donc un peu de script.
Et cet écouteur ne peut pas vivre sur le groupe. Un keydown ne remonte que
depuis l’élément qui a le focus ; or la condition parle du pointeur autant que
du focus, et le pointeur ne donne le focus à personne. Le contenu ouvert au seul
survol laisse le focus ailleurs dans la page : la touche part de là, le groupe
ne la voit jamais, et rien ne se referme. C’est le document qui écoute — c’est
le seul endroit par où la touche passe dans les deux cas. En contrepartie,
l’écouteur reçoit tous les Échap de la page : estAffiche() lui dit
si ce groupe-ci a quelque chose à refermer, faute de quoi il capterait la touche
au nez d’une boîte de dialogue ouverte ailleurs.
Trois points qui se ratent :
L’écouteur du document est le seul qui couvre le survol. Posé sur le groupe, il ne traite que la moitié du test 10.13.1 — celle où le contenu a été ouvert au focus.
Le refus ne vaut que pour la visite en cours. Une fois le pointeur et le focus
sortis du groupe, la classe renvoye se retire, sans quoi un contenu refermé
une fois ne reparaîtrait jamais.
Et si le focus se trouvait dans le contenu qu’on masque, il faut le rendre au déclencheur. Le laisser disparaître avec son hôte le renvoie au début de la page : c’est précisément ce que le test 7.3.2 interdit.
5. Du texte, ou des choses à cliquer ?
La réponse change le composant.
Pour du texte seul, role="tooltip" sur le contenu et aria-describedby du
déclencheur vers lui. Une infobulle ne contient ni lien ni bouton : rien n’y est
atteignable au clavier, et le motif ARIA ne le prévoit pas.
Dès qu’il y a quelque chose à cliquer — un aperçu avec un lien, un sous-menu
— ce n’est plus une infobulle mais un dépliage : aria-expanded sur le
déclencheur, aria-controls vers le contenu, et le contenu placé juste après le
déclencheur dans l’ordre du DOM pour que la tabulation y entre naturellement.
C’est là que joue le critère 12.11.
6. En avez-vous besoin ?
Un contenu additionnel est du contenu qu’on cache. Avant d’en écrire un, demandez-vous ce que coûterait de l’écrire dans la page : une définition sous le terme, une phrase d’aide sous le champ, une liste de rubriques dépliée. Rien à survoler, rien à refermer, rien à tester.
Le contenu additionnel se justifie quand il serait envahissant partout et utile par endroits. Pas quand il sert à faire tenir une page trop chargée.
Au clavier
| Touche | Effet |
|---|---|
| Tab | Amène le focus au déclencheur, et le contenu apparaît. |
| Tab (encore) | Entre dans le contenu s’il renferme un lien ou un bouton ; sinon passe au déclencheur suivant. |
| Maj + Tab | Ressort du contenu vers le déclencheur, sans rien refermer. |
| Échap | Referme le contenu, y compris quand il n’était ouvert qu’au survol. Le focus ne bouge pas — sauf s’il était dans le contenu refermé, où il revient au déclencheur. |
| Entrée / Espace | Sur un déclencheur bouton : affiche ou masque le contenu, pour qui préfère ne pas dépendre du focus. |
Ce que le lecteur d’écran restitue
Sur une infobulle : le nom, la nature, puis la description — de l’ordre de « taux de conformité, bouton, le rapport des critères conformes aux critères applicables ». Sur un aperçu dépliable : « critère 10.13, bouton, réduit », puis « développé » à l’ouverture. La formulation varie d’un lecteur à l’autre, les informations non.
Le diagnostic inverse est ici particulier. aria-describedby ne restitue rien
tant que le contenu visé est masqué par display: none — et c’est normal. Si
vous n’entendez pas la description, la cause la plus fréquente n’est pas
l’attribut : c’est que le contenu n’apparaît pas à la prise de focus, donc reste
masqué au moment où le lecteur voudrait le lire. C’est le critère 10.14 qui se
manifeste sous les traits d’un problème d’ARIA.
Si vous entendez la description mais que Tab ne mène nulle part dans l’aperçu, le contenu n’est pas placé après le déclencheur dans le DOM, ou il se referme à la sortie du focus.
Checklist
- Un déclencheur focalisable — un
<button>ou un<a href>, jamais unspan. - Le contenu affiché au survol et à la prise de focus, par le même groupe.
:hoveret:focus-withinposés sur le groupe, pas sur le déclencheur seul.- Aucun vide entre le déclencheur et le contenu :
paddingtransparent, pasmargin. - Échap referme, sans exiger de déplacer le pointeur ni le focus : l'écouteur est sur le document, pas sur le groupe.
role="tooltip"pour du texte seul ;aria-expandeddès qu’il y a à cliquer.- Le contenu placé juste après le déclencheur dans l’ordre du DOM.
Les critères RGAA en jeu
- Critère 10.13 — Contenus au survol ou au focus
Les trois conditions du référentiel tiennent ici : le contenu doit pouvoir être survolé sans disparaître, être refermé sans déplacer le pointeur ni le focus, et rester affiché tant que l'un des deux ne l'a pas quitté.
- Critère 10.14 — Contenus additionnels via CSS
Un contenu que seule la règle :hover révèle n'existe ni pour le clavier ni pour le tactile ; et poser :focus sur un span ne change rien, puisqu'un span ne prend jamais le focus.
- Critère 12.11 — Contenus additionnels au clavier
Un aperçu ou un sous-menu qui contient un lien doit rester ouvert le temps qu'on tabule dedans : refermé dès que le pointeur quitte le déclencheur, son contenu est inatteignable au clavier.
- Critère 7.3 — Contrôle des scripts au clavier
Un contenu ouvert par un gestionnaire d'événement de souris seul ne s'ouvre pour personne d'autre, et le refermer ne doit pas escamoter l'élément qui portait le focus.
Vérifiez votre composant avec l'extension
Pour aller plus loin
- Critère 10.13 — Contenus au survol ou au focus et 12.11 — Contenus additionnels au clavier : la méthode de vérification, geste par geste.
- Le bouton-menu : le même contenu qui s’ouvre, mais à l’activation plutôt qu’au survol — et pourquoi c’est plus sûr.
- Le menu de navigation : le cas du sous-menu, où la moitié des rubriques du site se joue sur ce trajet du pointeur.
- « Composant d’interface » au glossaire : la définition qui décide de ce qui déclenche ces critères.
Sources
- ARIA APG — Tooltip Pattern — le contrat de comportement d’une infobulle
- MDN —
:focus-within— la pseudo-classe qui traite le focus comme le survol - WCAG — Understanding 1.4.13 Content on Hover or Focus — l’origine des trois conditions
- Sarah Higley — Tooltips in the time of WCAG 2.1 — l’enquête de référence sur les infobulles et leurs impasses