Infobulle (Tooltip)
Un conteneur d'information contextuelle courte s'affichant au survol ou au focus d'un élément interactif.
1. Démonstration
2. Accessibilité (L'exigence du critère WCAG 1.4.13)
Les infobulles sont souvent le cauchemar des utilisateurs de lecteurs d'écran ou de loupes d'écran. Pour être accessible (et valider l'audit RGAA), une infobulle doit respecter la règle P.S.M. (Persistante, Survolable, Masquable) :
| Règle RGAA / WCAG | Implémentation technique |
|---|---|
| Lien sémantique | L'élément déclencheur (ici un <button>) est lié à l'infobulle par l'attribut aria-describedby="ID_DE_L_INFOBULLE". Le lecteur d'écran lira d'abord le bouton, puis l'infobulle après une courte pause. |
| Rôle explicite | L'infobulle porte l'attribut role="tooltip". |
| M - Masquable | L'utilisateur doit pouvoir faire disparaître l'infobulle en appuyant sur la touche Échap, sans déplacer son focus. (Géré par notre micro-JS). |
| S - Survolable | Si l'utilisateur agrandit son écran (loupe), il doit pouvoir déplacer son pointeur de souris sur l'infobulle elle-même sans qu'elle ne disparaisse. (Géré via pointer-events: auto et un positionnement contigu). |
| P - Persistante | L'infobulle reste visible tant que le survol ou le focus est actif, ou jusqu'à ce que l'utilisateur la masque. |
3. Code source
HTML
L'infobulle doit immédiatement suivre son élément déclencheur dans le code HTML pour que le positionnement CSS relatif fonctionne sans calculs complexes.
<div class="tooltip-wrapper">
<button type="button" aria-describedby="helper-txt">
Exemple de déclencheur
</button>
<span role="tooltip" id="helper-txt" class="tooltip">
Texte d'aide concis et informatif.
</span>
</div>
CSS
En utilisant visibility: hidden et opacity: 0, on s'assure que l'infobulle est totalement masquée pour les technologies d'assistance tant qu'elle n'est pas active.
/* Conteneur de positionnement */
.tooltip-wrapper {
position: relative;
display: inline-block;
}
/* L'infobulle elle-même (Style inversé très contrasté) */
.tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
/* margin-bottom: 8px; */
background-color: var(--text-color); /* Fond jaune en thème sombre, noir en clair */
color: var(--bg-color); /* Texte noir en thème sombre, blanc en clair */
padding: 0.5rem 0.75rem;
font-size: calc(var(--base-font-size) - 0.2rem);
font-weight: 700;
white-space: nowrap;
border-radius: 4px;
z-index: 10;
/* Masquage initial complet */
visibility: hidden;
opacity: 0;
pointer-events: auto; /* Permet le survol de l'infobulle elle-même (Règle WCAG) */
transition: opacity var(--transition-speed) ease, visibility var(--transition-speed) ease;
}
/* Petite flèche sous l'infobulle */
.tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border-width: 6px;
border-style: solid;
border-color: var(--text-color) transparent transparent transparent;
}
/* Affichage au survol ou au focus de l'élément parent */
.tooltip-wrapper:hover .tooltip,
.tooltip-wrapper:focus-within .tooltip {
visibility: visible;
opacity: 1;
}
.tooltip-wrapper:hover .tooltip {
margin-bottom: 8px;
}
.tooltip-wrapper:focus-within .tooltip {
margin-bottom: 14px;
}
/* Classe de masquage temporaire déclenchée par la touche Échap */
.tooltip.is-hidden {
visibility: hidden !important;
opacity: 0 !important;
}
JavaScript (Vanilla)
Pour respecter à 100% le critère de masquage de la touche Échap, ce script écoute le clavier. Si une infobulle est visible et que l'utilisateur appuie sur Échap, on lui ajoute une classe .is-hidden. Dès que la souris sort ou que le focus change, on retire la classe pour que l'infobulle puisse s'afficher à nouveau le coup d'après.
// --- Gestion de l'accessibilité des Infobulles (Touche Échap) ---
const tooltipWrappers = document.querySelectorAll('.tooltip-wrapper');
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' || e.key === 'Esc') {
const activeTooltip = document.querySelector('.tooltip-wrapper:hover .tooltip, .tooltip-wrapper:focus-within .tooltip');
if (activeTooltip) {
activeTooltip.classList.add('is-hidden');
}
}
});
// Réinitialiser l'état d'affichage dès qu'on quitte l'élément
tooltipWrappers.forEach(wrapper => {
const resetTooltip = () => {
const tooltip = wrapper.querySelector('.tooltip');
if (tooltip) tooltip.classList.remove('is-hidden');
};
wrapper.addEventListener('mouseleave', resetTooltip);
wrapper.addEventListener('focusout', resetTooltip);
});