Tooltip accessible : role tooltip, aria-describedby, RGAA
Implémenter une infobulle role=tooltip conforme RGAA : apparition au focus, fermeture par Échap, aria-describedby ou aria-labelledby, et pourquoi title n'est pas une infobulle.
Une infobulle (tooltip) est une petite fenêtre de texte descriptif qui apparaît au survol ou à la prise de focus d'un élément, sans jamais recevoir le focus elle-même. Il n'existe pas d'élément natif, et l'attribut title n'en est PAS un : il n'apparaît jamais au focus clavier, est invisible au tactile, non stylable, non rejetable, et restitué de façon incohérente selon les lecteurs d'écran. Ce n'est pas une infobulle au rabais, c'est une infobulle cassée.
Cette fiche est le contrat de référence du motif : critères, clavier, ARIA, défauts. Pour le pas-à-pas complet côté implémentation : Tooltips accessibles : bonnes pratiques et implémentation.
Quand ce motif est le mauvais choix
L'infobulle ne convient qu'à une courte description textuelle de son déclencheur. Tout le reste appelle un autre motif :
| Contenu | Motif correct |
|---|---|
| Courte description textuelle du déclencheur | Infobulle (cette fiche) |
| Un lien ou un bouton à l'intérieur | Contenu dépliable (disclosure) ou dialogue non modal |
| Contenu riche, plusieurs paragraphes | Contenu dépliable |
| Consigne indispensable d'un champ (format attendu…) | Aide visible en permanence, reliée au champ par aria-describedby |
| Liste d'actions | Bouton de menu |
La ligne des champs de formulaire compte double : une consigne de format cachée dans une infobulle est invisible au tactile et facile à manquer. Affichez-la. Quant au contenu interactif, une infobulle ne peut structurellement pas le rendre atteignable : dès que Tab quitte le déclencheur, l'infobulle disparaît et sa cible avec.
Critères RGAA applicables
- Critère 12.11 : contenus additionnels atteignables au clavier. Le critère central du motif ; ses notes techniques citent précisément l'infobulle personnalisée proposant un lien. Un contenu additionnel contenant des composants d'interface doit être atteignable au clavier, ce qu'une infobulle ne permet jamais : donc aucun contenu interactif dans une infobulle.
- Critère 10.13 : contenus additionnels contrôlables. L'infobulle doit pouvoir être masquée sans déplacer le focus ni le pointeur (Échap, test 10.13.1), rester survolable sans disparaître (test 10.13.2) et rester visible tant que le focus ou le pointeur est sur le déclencheur ou sur elle (test 10.13.3).
- Critère 10.14 : contenus additionnels via CSS rendus visibles au clavier. Une infobulle affichée uniquement par CSS au survol (
:hover) doit aussi apparaître à la prise de focus. - Critère 7.1 : compatibilité des scripts avec les technologies d'assistance. L'infobulle doit être associée à son déclencheur via
aria-describedby(ouaria-labelledbypour un bouton-icône sans texte), sans quoi elle n'est jamais annoncée. - Critère 7.3 : script contrôlable au clavier et par tout dispositif de pointage. Une infobulle gérée par script qui n'apparaît qu'au survol est inutilisable au clavier ; elle doit aussi se déclencher au focus.
- Critère 10.7 : prise de focus visible. Le déclencheur doit garder un indicateur de focus visible pendant que l'infobulle est affichée.
- Critère 3.2 : contraste du texte. Le texte de l'infobulle doit atteindre le contraste requis sur son propre fond (4.5:1 pour du texte courant, test 3.2.1) ; une infobulle pâle sur fond sombre est un oubli fréquent.
Interaction clavier attendue
| Touche | Action |
|---|---|
| Tab | La prise de focus sur le déclencheur affiche l'infobulle ; le départ du focus la masque. |
| Échap | Masque l'infobulle sans déplacer le focus, qui reste sur le déclencheur. |
Le modèle est volontairement minimal : l'infobulle ne reçoit jamais le focus. Côté pointeur, elle doit rester survolable : un utilisateur zoomé à 200 % peut avoir besoin d'amener le pointeur sur l'infobulle elle-même pour la lire ; ne la masquez pas dès que le pointeur quitte le déclencheur, prévoyez un court délai.
Rôles et attributs ARIA
role="tooltip" sur le conteneur du texte, avec un id ; aria-describedby sur le DÉCLENCHEUR, pointant cet id : c'est cette association qui fait annoncer l'infobulle. Elle doit rester dans le DOM même masquée, sinon la référence est pendante. Exception : quand l'infobulle EST le nom d'un bouton-icône sans texte visible, utilisez aria-labelledby à la place (une description ne nomme pas un bouton), jamais les deux à la fois.
<!-- L'infobulle DÉCRIT un bouton qui a déjà un nom visible. -->
<button type="button" id="bouton-enregistrer" aria-describedby="infobulle-enregistrer">
Enregistrer
</button>
<!-- role="tooltip", et toujours dans le DOM pour que aria-describedby résolve. -->
<div role="tooltip" id="infobulle-enregistrer" hidden>
Enregistre le brouillon local, sans rien publier.
</div>
<!-- Bouton-icône SANS texte visible : l'infobulle EST le nom. aria-labelledby,
pas aria-describedby : un bouton sans nom avec une simple description est
annoncé « bouton » tout court (critère 7.1). -->
<button type="button" aria-labelledby="infobulle-supprimer">
<svg aria-hidden="true" focusable="false" width="16" height="16">
<use href="#icone-corbeille"/>
</svg>
</button>
<div role="tooltip" id="infobulle-supprimer" hidden>Supprimer</div>
[role="tooltip"] {
position: absolute;
max-inline-size: 20rem;
padding: 0.5rem 0.75rem;
background: #1a1a1a;
color: #fff; /* vérifier au moins 4.5:1 sur ce fond (critère 3.2) */
}
/* Le déclencheur garde son indicateur de focus pendant que l'infobulle
est affichée (critère 10.7). */
button:focus-visible {
outline: 2px solid #0056b3;
outline-offset: 2px;
}
/* Pas de pointer-events: none sur l'infobulle : elle doit rester survolable
pour qu'un utilisateur zoomé puisse amener le pointeur dessus (test 10.13.2). */
const declencheur = document.getElementById('bouton-enregistrer');
const infobulle = document.getElementById('infobulle-enregistrer');
let timerMasquage;
const afficher = () => {
clearTimeout(timerMasquage);
infobulle.hidden = false;
};
// Le masquage est différé : le pointeur doit pouvoir voyager du déclencheur
// vers l'infobulle sans qu'elle disparaisse en chemin (test 10.13.2).
const masquerBientot = () => {
timerMasquage = setTimeout(() => { infobulle.hidden = true; }, 150);
};
// Focus ET survol affichent l'infobulle (critères 7.3 et 10.14).
declencheur.addEventListener('focus', afficher);
declencheur.addEventListener('blur', () => { infobulle.hidden = true; });
declencheur.addEventListener('mouseenter', afficher);
declencheur.addEventListener('mouseleave', masquerBientot);
// L'infobulle reste ouverte tant que le pointeur est dessus.
infobulle.addEventListener('mouseenter', afficher);
infobulle.addEventListener('mouseleave', masquerBientot);
// Échap masque sans déplacer le focus (test 10.13.1).
document.addEventListener('keydown', (evenement) => {
if (evenement.key === 'Escape') infobulle.hidden = true;
});
À ne pas faire :
<!-- L'attribut title en guise d'infobulle : jamais affiché au focus clavier,
invisible au tactile, non rejetable, restitué de façon incohérente. -->
<button type="button" title="Enregistre le brouillon local">Enregistrer</button>
<!-- Infobulle sur un élément non focalisable : un utilisateur clavier ne
peut jamais l'atteindre (critère 12.11). Si un élément mérite une
infobulle, il mérite d'être un bouton. -->
<span class="icone-info" aria-describedby="infobulle">ⓘ</span>
<!-- Infobulle sur un bouton disabled : non focalisable, sans événements de
pointeur. L'infobulle qui explique POURQUOI le bouton est désactivé est
exactement celle que personne ne peut lire. Préférer aria-disabled="true"
sur un bouton restant focalisable. -->
<button type="button" disabled aria-describedby="pourquoi-desactive">Envoyer</button>
<!-- Contenu interactif dans l'infobulle : inatteignable. Tab quitte le
déclencheur, l'infobulle disparaît, le lien avec (critère 12.11). -->
<div role="tooltip" id="infobulle">
Voir la <a href="/documentation">documentation</a> pour le détail.
</div>
<!-- aria-labelledby ET aria-describedby vers la même infobulle : le texte
est annoncé deux fois. Un seul des deux. -->
<button aria-labelledby="infobulle" aria-describedby="infobulle">…</button>
<!-- role="tooltip" sans aria-describedby ni aria-labelledby qui pointe vers
lui : le rôle seul n'associe rien, l'infobulle n'est jamais annoncée. -->
<button type="button">Enregistrer</button>
<div role="tooltip">Enregistre le brouillon local.</div>
// À ne pas faire : survol uniquement. L'infobulle n'existe pas pour
// l'utilisateur clavier (critères 7.3 et 12.11).
declencheur.addEventListener('mouseenter', afficher);
declencheur.addEventListener('mouseleave', masquer);
// À ne pas faire : masquage immédiat au mouseleave. Le pointeur ne peut pas
// traverser l'espace entre déclencheur et infobulle ; l'utilisateur zoomé ne
// peut jamais la lire (test 10.13.2).
declencheur.addEventListener('mouseleave', () => { infobulle.hidden = true; });
Défauts fréquents et impact utilisateur
- Attribut
titleutilisé comme infobulle : jamais affiché à la prise de focus clavier, invisible au tactile, restitué de façon incohérente ; les utilisateurs clavier et mobiles sont privés du contenu (letitle, contrôlé par le navigateur, relève d'un cas particulier du critère 10.13, mais l'information reste hors de portée en pratique). - Apparition au survol uniquement : l'infobulle n'existe pas pour l'utilisateur clavier.
- Infobulle sur un élément non focalisable (
<span>, boutondisabled) : inatteignable au clavier ; cas typique, l'infobulle qui explique pourquoi un bouton est désactivé est exactement celle que personne ne peut lire. - Lien ou bouton dans l'infobulle : inatteignable ; dès que Tab quitte le déclencheur, l'infobulle disparaît et sa cible avec. Le besoin réel est un contenu dépliable.
- Disparition immédiate au
mouseleaveoupointer-events: none: le pointeur ne peut pas traverser l'espace entre déclencheur et infobulle ; l'utilisateur zoomé ou en basse vision ne peut jamais la lire. aria-labelledbyETaria-describedbyvers la même infobulle, outitleen doublon : le texte est annoncé deux fois (« Supprimer, Supprimer »).
Ce que les outils automatiques ne détectent pas
Un scanner automatique, le nôtre compris, repère l'absence de nom accessible d'un bouton-icône, et c'est utile. Mais l'essentiel des défauts réels d'une infobulle reste manuel :
- l'apparition à la prise de focus, pas seulement au survol : seul un parcours à Tab le révèle ;
- l'usage de
titleen guise d'infobulle et la présence de contenu interactif dans l'infobulle, qui relèvent d'un choix de motif, pas d'un attribut invalide ; - la fermeture par Échap sans déplacement du focus, et la persistance au survol de l'infobulle elle-même ;
- le déclencheur non focalisable, que rien ne signale tant qu'on ne parcourt pas la page au clavier.
Vérifier ce composant
Protocole manuel rapide :
- Au clavier seul : tabulez jusqu'au déclencheur, l'infobulle doit apparaître. Si elle n'apparaît qu'au survol, les critères 7.3 et 12.11 sont en échec. Échap doit la masquer, focus toujours sur le déclencheur.
- Le test du critère 12.11 : l'infobulle contient-elle un lien ou un bouton ? Si oui, ce contenu est inatteignable ; le motif correct était un contenu dépliable.
- Zoomé à 200 % : survolez le déclencheur puis amenez le pointeur sur l'infobulle. Elle doit rester ouverte. Si elle disparaît pendant la traversée, le masquage est immédiat au
mouseleave. - Au lecteur d'écran : pour un déclencheur nommé, attendez-vous à « Enregistrer, bouton » puis le texte de l'infobulle en description. Si le texte est annoncé deux fois,
aria-labelledbyetaria-describedbysont posés ensemble, ou untitletraîne.
Deux outils gratuits du site pour appuyer la vérification : le simulateur de lecteur d'écran affiche l'arbre d'accessibilité, où l'association déclencheur-infobulle (nom ou description) se lit directement, et le calculateur de contraste valide le texte de l'infobulle sur son propre fond (critère 3.2).
Approfondir avec les fiches critères
Vérifier ce composant sur votre site ?
Le scan repère les défauts détectables automatiquement (rôles incohérents, champs sans étiquette, attributs ARIA orphelins) ; le reste se vérifie à la main avec les protocoles de cette fiche.
Lancer un scan gratuit