Composant · Afficher et masquer

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 :

ContenuMotif correct
Courte description textuelle du déclencheurInfobulle (cette fiche)
Un lien ou un bouton à l'intérieurContenu dépliable (disclosure) ou dialogue non modal
Contenu riche, plusieurs paragraphesContenu dépliable
Consigne indispensable d'un champ (format attendu…)Aide visible en permanence, reliée au champ par aria-describedby
Liste d'actionsBouton 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

Interaction clavier attendue

ToucheAction
TabLa prise de focus sur le déclencheur affiche l'infobulle ; le départ du focus la masque.
ÉchapMasque 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 title utilisé 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 (le title, 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>, bouton disabled) : 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 mouseleave ou pointer-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-labelledby ET aria-describedby vers la même infobulle, ou title en 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 title en 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 :

  1. 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.
  2. 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.
  3. 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.
  4. 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-labelledby et aria-describedby sont posés ensemble, ou un title traî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

Toutes les fiches composants accessibles