TooltipsUXFrontendReactCSSAccessibilité

Tooltips : le bon délai d'affichage change tout

Un pattern UX simple mais puissant : 200ms de délai puis 300ms de cooldown pour des tooltips qui ne polluent pas la navigation. Guide React et CSS.

31 août 2026
Tooltips : le bon délai d'affichage change tout

En bref

  • 1Un délai de 200ms sur l'ouverture des tooltips évite les popups intempestives lors du survol rapide d'une interface, mais ralentit l'utilisateur qui navigue intentionnellement entre plusieurs éléments.
  • 2Le pattern « warm window » résout le problème : 300ms de cooldown après la fermeture d'un tooltip pendant lesquels le suivant s'ouvre instantanément, sans animation ni attente.
  • 3Deux implémentations viables existent : une version React avec Provider/useRef, et une version HTML/CSS native utilisant popover='hint' et interest-delay-start, encore partiellement supportée.

Tooltips : le bon délai d'affichage change tout

Un micro-détail qui fait la différence entre une UI soignée et une UI frustrante

Un tooltip qui s'affiche au quart de tour quand on promène la souris sur une page, c'est pénible. Un tooltip qui met 500ms à apparaître sur chaque élément qu'on survole, c'est tout aussi pénable. Le juste milieu existe, et il tient en deux chiffres : 200ms de délai, 300ms de cooldown.

Cet article décortique le pattern "delayed-then-instant" popularisé par Abhishek Jakhar sur Master.dev, et montre comment l'implémenter proprement, que vous soyez en React pur ou en HTML/CSS natif.

Le problème : des tooltips qui parasitent la navigation

Si vous avez déjà passé la souris sur une barre d'outils, vous avez vécu ça : un balayage horizontal déclenche une cascade de tooltips intempestifs. Chaque micro-passage ouvre une popup, puis la referme, créant une expérience saccadée.

Le réflexe naturel, c'est de mettre un délai. Mais un délai fixe de 200ms sur tous les tooltips résout un problème... et en crée un autre : quand on navigue volontairement d'un élément à l'autre, on attend 200ms à chaque fois. Ça semble court, mais multiplié par 3-4 éléments adjacents, l'utilisateur a l'impression que l'interface traîne.

Contexte visuel : imaginez une rangée de logos d'entreprises, chacun avec un tooltip au hover. Sans délai, chaque passage de souris ouvre un tooltip. Avec un délai fixe, passer d'un logo au suivant signifie attendre 200ms à chaque fois. Insupportable.

Le pattern warm/cold window

La solution est élégante : au lieu d'un délai binaire (ouvert ou fermé), on introduit un état intermédiaire.

Comment ça marche

  1. L'utilisateur survole un élément → un compteur de 200ms démarre.
  2. Si la souris reste au-delà de 200ms → le tooltip s'ouvre et la page devient "warm".
  3. La souris quitte l'élément → le tooltip se ferme, un cooldown de 300ms démarre.
  4. Si l'utilisateur survole un autre élément pendant le cooldown → tooltip instantané, sans attente.
  5. Une fois le cooldown expiré (300ms sans hover) → la page redevient "cold", le délai de 200ms est rétabli.
hover → 200ms → tooltip ouvert (page warm)
leave → fermeture tooltip → 300ms cooldown
         ├─ hover avant fin cooldown → ouverture instantanée, page reste warm
         └─ cooldown fini → page froide, délai 200ms rétabli

Pourquoi 200ms ?

Ce n'est pas un chiffre sorti d'un chapeau. Les guidelines Nielsen Norman Group et les travaux sur l'UX des tooltips (voir StackExchange UX, 2010) convergent vers une fourchette de 300-500ms pour le délai standard. Mais dans le cadre d'une navigation active entre plusieurs éléments proches, 200ms est un bon compromis :

  • En dessous de 150ms : le tooltip s'ouvre trop facilement, on revient au problème initial.
  • Entre 150ms et 250ms : zone idéale pour filtrer les passages involontaires sans pénaliser l'intention.
  • Au-dessus de 250ms : la latence devient perceptible et l'interface semble molle.

Le cooldown de 300ms, lui, correspond au temps que met un utilisateur pour passer d'un élément à l'autre dans une rangée dense (logos, icônes, boutons). Assez long pour couvrir le mouvement, assez court pour ne pas maintenir l'état warm en permanence.

Implémentation React

L'article original de Master.dev détaille une implémentation React propre. Le cœur du système repose sur un Provider partagé qui expose l'état "warm" via le Context API.

Le Provider (état partagé)

const warm = useRef(false);
const cooldownTimer = useRef(null);

const tooltips = useMemo(() => ({
  openDelay,      // 200ms
  isWarm: () => warm.current,
  markOpened() {
    warm.current = true;
    clearTimeout(cooldownTimer.current);
  },
  markClosed() {
    clearTimeout(cooldownTimer.current);
    cooldownTimer.current = setTimeout(() => {
      warm.current = false;
    }, 300);  // warmFor = 300ms
  },
}), [openDelay, warmFor]);

Points clés : isWarm est stocké dans une ref (pas un state React), car le modifier ne doit pas re-render tous les tooltips de la page. Elle n'est lue que dans les event handlers (onMouseEnter, onMouseLeave).

Le composant Tooltip

function handleEnter() {
  if (tooltips.isWarm()) {
    show();        // ouverture instantanée
    return;
  }
  openTimer.current = setTimeout(show, tooltips.openDelay);
}

function handleLeave() {
  clearTimeout(openTimer.current);
  if (!open) return;       // tooltip pas encore ouvert, on annule
  setOpen(false);
  tooltips.markClosed();   // démarre le cooldown 300ms
}

La touche CSS

Le composant copie l'état isWarm dans un data attribute data-instant, que le CSS utilise pour supprimer l'animation d'entrée :

.tooltip[data-instant="true"] {
  transition-duration: 0ms;
}

Résultat : quand le contexte est warm, le tooltip apparaît immédiatement, sans fondu, sans délai. Net et précis.

L'alternative HTML/CSS native (sans JavaScript)

Chris Coyier a poussé le pattern plus loin en montrant qu'on peut obtenir exactement le même résultat avec uniquement du HTML et du CSS — pas une ligne de JS.

Le HTML

<button interestfor="tip-1" aria-labelledby="tip-1">
  <svg>...</svg>
</button>
<span id="tip-1" popover="hint">
  Je suis le tooltip
</span>

⚠️ L'attribut popover="hint" est une nouveauté HTML qui lie l'ouverture au "interest" (hover, focus, etc.). Le navigateur gère tout seul l'affichage/ masquage.

Le CSS

button {
  interest-delay-start: 200ms;
  interest-delay-end: 150ms;
}

/* Quand un tooltip est ouvert dans la zone,
   on supprime le délai pour les suivants */
.area:has(:popover-open) button {
  interest-delay-start: 0s;
}

C'est tout. En deux règles CSS et un attribut HTML, on reproduit le pattern warm/cold sans useRef, sans setTimeout, sans Provider.

Fallback pour les navigateurs qui ne supportent pas

Safari ne supporte pas encore popover="hint". Chris propose un fallback astucieux utilisant une propriété CSS personnalisée --warm et une transition :

@supports not (interest-delay-start: 0s) {
  .area {
    --warm: 0;
    transition: --warm 0s 700ms;
    &:hover {
      --warm: 1;
      transition: --warm 0s 250ms;
    }
  }

  [popover] {
    transition-delay: calc((1 - var(--warm)) * 200ms);
  }
}

Comparaison des approches

CritèreReact (Abhishek)HTML/CSS natif (Chris)
CompatibilitéUniversellepopover="hint" : Chrome + Firefox (pas Safari)
PerformanceRe-renders maîtrisés (refs)Pas de JS, zéro surcoût
MaintenabilitéDépend de la stack ReactFonctionne dans n'importe quel projet HTML
Contrôle finAnimation, transition, customisation avancéeLimitée aux capacités CSS
AccessibilitéWCAG 1.4.13 manuelpopover="hint" + aria-labelledby automatique

Mon conseil : si vous utilisez déjà une lib comme Radix UI ou MUI, l'approche React s'intègre naturellement dans votre code existant (avec un simple Provider à ajouter). Si vous partez d'une page statique ou d'un projet minimaliste, la version CSS native est plus élégante et plus rapide.

Accessibilité : WCAG 1.4.13

Ne pas oublier que les tooltips sont couverts par le critère WCAG 2.2 1.4.13 (Content on Hover or Focus). Trois règles impératives :

  1. Dismissible : l'utilisateur doit pouvoir fermer le tooltip sans déplacer la souris (touche Échap).
  2. Hoverable : la souris doit pouvoir survoler le tooltip sans qu'il disparaisse.
  3. Persistant : le tooltip reste visible tant que le hover/focus ou l'information est valide.

Dans l'implémentation React, ces trois points se gèrent avec :

  • Un onKeyDown={(e) => e.key === 'Escape' && close()}.
  • Un onMouseEnter qui ne déclenche pas handleLeave (le tooltip reste).
  • Un pointeur de fermeture explicite si besoin.

L'approche native popover="hint" gère ces contraintes automatiquement, modulo l'ajout des attributs aria-labelledby sur le trigger et role="tooltip" sur le contenu.

Ce qu'il faut retenir

  • 200ms de délai, 300ms de cooldown : un pattern simple, validé par l'expérience et les guidelines UX.
  • Deux implémentations : React (Provider + useRef) ou CSS natif (popover hint + interest-delay-start).
  • Accessibilité obligatoire : WCAG 1.4.13 s'applique à tous les tooltips personnalisés.
  • Le diable est dans les détails : un délai fixe partout est pire que pas de délai du tout. Le warm/cold window est la solution éprouvée.

Et si vous utilisez une lib comme Radix ou Material UI : regardez leur API de "delay" ou "enterDelay". Beaucoup proposent déjà les hooks nécessaires. Parfois, il suffit de passer une prop enterDelay={200} et de wrapper dans un Provider custom.

La différence entre un produit qui fonctionne et un produit poli tient à des micro-détails comme celui-ci. Les tooltips, c'est un exemple parfait : tellement banal qu'on n'y pense pas, mais tellement important quand on les utilise tous les jours.

Questions fréquentes

Un délai de 200ms à 500ms est recommandé par les guidelines UX (NN/g, WCAG). 200ms équilibre bien réactivité et filtrage des passages de curseur involontaires. En dessous de 150ms, le tooltip s'ouvre trop facilement ; au-dessus de 250ms, la latence devient perceptible.

En React, créez un TooltipProvider avec un état 'warm' partagé via useRef et useContext. Au hover, si le contexte est warm, le tooltip s'ouvre instantanément ; sinon, un setTimeout de 200ms démarre. À la fermeture, un cooldown de 300ms est lancé. En CSS natif, utilisez interest-delay-start (200ms) combiné à :has(:popover-open) pour passer à 0s quand la page est warm.

Oui, si vous respectez le critère 1.4.13 (Content on Hover or Focus) : le contenu doit être dismissible (touche Échap), hoverable (la souris peut le survoler sans le faire disparaître), et persistant (ne disparaît pas tant que le focus ou le hover est maintenu). Les tooltips natives HTML title ne sont pas concernées car gérées par le navigateur.

Articles connexes

Vous avez aimé cet article ?