Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
09 avril 2026

popover=hint : sur une landing c’est mignon, en vraie UI ça se bagarre

popover=hint a un côté « tooltip natif » séduisant. Sauf qu’avec des overlays et des composants imbriqués, ses règles de fermeture deviennent vite imprévisibles.

11 min de lecture
61 vues
réactions
Partager :
popover=hint : sur une landing c’est mignon, en vraie UI ça se bagarre

Tu as sûrement eu la même réaction que moi en voyant popover=hint : enfin un truc natif, simple, pour faire des micro-aides sans trimballer une lib. Et puis tu l’essaies sur une démo clean, tu te dis « ok c’est plié ». Sauf que dès que ton UI ressemble à une vraie app (menus, dialogs, drawers, popovers imbriqués, portails), tu peux tomber sur des comportements franchement chelous : ça se ferme quand tu ne veux pas, ça ne se ferme pas quand tu veux, et l’imbrication change les règles sans prévenir.

L’idée de ce billet, c’est de te donner une lecture “prod” de popover=hint : ce que ça promet, ce qui est actuellement incohérent (notamment en nested), et comment décider si tu peux le ship ou si tu dois rester sur une stratégie plus explicite.

popover=hint : ce que tu crois acheter (et ce que le navigateur essaie de faire)

La Popover API HTML est pensée pour gérer des petites couches d’UI “au-dessus” du contenu, avec une notion de pile (stack), de fermeture automatique selon les interactions, et un câblage simple via popovertarget. Dans le monde idéal, tu poses un attribut, tu n’écris presque pas de JS, et tu laisses le navigateur gérer la vie.

popover="auto" vise les popovers qui se ferment “tout seuls” selon des règles standard (typiquement, clic à l’extérieur, ouverture d’un autre popover auto, etc.). popover="manual" te laisse gérer. popover="hint" cherche à être un entre-deux orienté “petite aide”, une couche moins lourde qu’un menu et moins intrusive qu’un dialog, avec des règles de fermeture censées mieux coller à des infobulles et des infos contextuelles.

Le problème, c’est que ces règles, une fois que tu sors de la démo, se mettent à dépendre de détails que tu ne maîtrises pas toujours, surtout l’imbrication DOM et l’interaction avec d’autres popovers dans la page.

Le point qui pique : l’imbrication change le comportement, parfois violemment

Le retour terrain le plus intéressant (et le plus inquiétant) autour de popover=hint, c’est l’incohérence entre un popover “non imbriqué” et le même popover rendu à l’intérieur d’un autre composant overlay. En gros, tu peux avoir un hint qui se comporte “comme un hint” quand il est à plat dans la page, puis qui commence à se comporter “comme un auto” quand il est nested, ou qui déclenche des fermetures inattendues d’autres popovers.

Dans une app moderne, ce cas n’est pas exotique. Entre les portails React/Vue, les wrappers de layout, les composants qui se rendent dans un body overlay, et les zones scrollables, ton « petit hint » se retrouve très vite dans une hiérarchie différente de celle que tu imagines. Et si les règles de fermeture tiennent compte de cette hiérarchie, tu te retrouves avec des bugs à la fois difficiles à expliquer et difficiles à reproduire proprement.

Concrètement, ce qui remonte aujourd’hui, c’est surtout un mélange de trois effets : des fermetures qui semblent dépendre du fait que le hint soit contenu (ou non) dans un autre popover, des interactions surprenantes avec des popovers en auto (un hint qui “compte” dans la pile auto, ou qui fait fermer un auto alors que l’utilisateur n’a pas vraiment quitté ce contexte), et des incohérences sur le clic extérieur selon la structure DOM.

Pourquoi ça devient vite ingérable en UI réelle (menus, dialogs, drawers, stacks)

Le piège classique, c’est quand ton hint vit à l’intérieur d’un menu (popover auto), lui-même dans un drawer, lui-même au-dessus d’une page qui a déjà un autre overlay. Tu veux juste une micro-aide près d’un champ ou d’un bouton. Mais le navigateur, lui, doit arbitrer : est-ce que ce clic est “extérieur” au hint ? est-ce qu’il est “extérieur” au menu ? est-ce que l’ouverture de ce hint doit fermer le menu parce que “un nouveau popover auto/hint arrive” ?

Dans une UI au clavier, ça se complique encore plus vite. Le focus bouge, parfois sans clic. Certains composants déplacent le focus volontairement (menu items, boutons, inputs), d’autres le font involontairement (auto-focus, restauration de focus). Si popover=hint décide de se fermer quand le focus sort, tu peux déclencher des fermetures en cascade en naviguant juste au Tab. Et tu te retrouves avec des “flickers” où l’aide apparaît puis disparaît parce que le focus a été déplacé par un composant parent.

Et sur mobile, le “clic extérieur” n’est plus un clic : c’est du touch, du scroll, parfois du pinch. Les overlays qui capturent des événements (ou qui posent des pointer-events bizarres) peuvent complètement changer ce que le navigateur perçoit comme une interaction “en dehors”.

Un exemple minimal : le HTML est simple, le comportement ne l’est pas

La promesse de l’API, c’est un câblage trivial. Et oui, ça marche. Le souci n’est pas « comment l’ouvrir », c’est « qui ferme quoi, et quand ».

<button popovertarget="help-email" popovertargetaction="toggle">
  ?
</button>

<div id="help-email" popover="hint">
  On n’utilise ton email que pour t’envoyer le reçu.
</div>

Dans une page simple, ça donne l’effet attendu. Dans une app avec un menu popover, un modal, ou un autre overlay, c’est là que tu dois vérifier si ton « petit hint » n’est pas en train de participer à une mécanique de fermeture globale que tu n’avais pas demandée.

Les contournements qui marchent en pratique (sans te lancer dans une usine à gaz)

La première stratégie, c’est l’isolation. Si ton hint est rendu à l’intérieur d’un composant overlay complexe, essaye de le sortir de cette hiérarchie, typiquement via un portal (React/Vue) ou en le plaçant à un endroit plus neutre du DOM. Ce n’est pas “plus propre” sur le papier, mais ça évite que le comportement du hint dépende de qui le contient. Et c’est exactement ce genre de dépendance implicite qui te coûte des heures.

La deuxième, c’est d’assumer que la fermeture automatique n’est pas fiable dans ton contexte, et de redevenir explicite. Ça ne veut pas dire réécrire tout le composant. Ça veut dire décider clairement de ton contrat produit : est-ce que le hint se ferme au clic extérieur, au blur, à Escape, à l’ouverture d’un autre overlay, ou seulement quand l’utilisateur reclique sur le bouton ? Ensuite, tu fais respecter ce contrat, même si le navigateur a sa propre opinion.

La troisième, quand tu as plusieurs overlays, c’est de gérer une notion de “stack” applicative. Pas forcément une grosse infra : juste un endroit où tu sais quel overlay est au-dessus, et qui a le droit de fermer quoi. La Popover API a sa pile, mais si tu découvres qu’elle ne colle pas à ta hiérarchie métier, tu vas souffrir tant que tu ne reprends pas la main.

Fermer explicitement : le petit JS qui te sauve d’un bug honteux

Quand j’ai un doute, je préfère être boring et verrouiller le comportement. Exemple classique : fermer le hint à Escape et au clic vraiment extérieur, mais sans provoquer la fermeture d’un parent (menu, drawer) si le produit ne le veut pas.

const hint = document.querySelector('#help-email');

document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && hint?.matches(':popover-open')) {
    hint.hidePopover();
  }
});

document.addEventListener('pointerdown', (e) => {
  if (!hint?.matches(':popover-open')) return;

  const target = e.target;
  // “Vrai” clic extérieur : ni dans le hint, ni sur son bouton (à adapter).
  if (target instanceof Node && !hint.contains(target)) {
    hint.hidePopover();
  }
});

Ce code n’est pas “la solution universelle”. Il illustre juste l’idée : quand le comportement natif te met en risque, tu poses une règle simple et tu la testes. Et tu assumes que tu es en train de définir le contrat UX, pas juste de “corriger un bug”.

Ce que je teste toujours avant de ship popover=hint (sinon tu vas te faire surprendre)

Je commence par les interactions à la souris et au touch, parce que c’est là que les fermetures inattendues se voient tout de suite. J’ouvre le hint, je clique dans la zone autour, je clique sur un autre bouton, je scroll dans un conteneur parent, je scroll la page. Si j’ai un menu popover ou un dialog dans le même écran, je fais la même chose en contexte : ouvrir le menu, ouvrir un hint dans le menu, cliquer ailleurs dans le menu, puis ailleurs en dehors. Je veux comprendre qui ferme quoi, sans interprétation.

Ensuite je fais le parcours clavier comme un utilisateur réel, pas comme un dev pressé. Tab pour entrer, Tab pour sortir, Shift+Tab pour revenir, et surtout Escape. J’observe si le focus se retrouve dans le vide, si le hint se ferme quand le focus passe sur un élément voisin, et si l’ouverture d’un hint casse la navigation du parent. Si tu as déjà eu des menus impossibles à naviguer parce qu’un tooltip vole le focus, tu vois le genre.

Enfin je teste le cas “composants imbriqués” qui ressemble à ta prod. Pas la story isolée. Le vrai écran, avec ses wrappers, ses portails, son header sticky, ses overlays. Si le comportement de hint change entre ces deux environnements, c’est un signal rouge immédiat : ça veut dire que ton UI dépend d’une règle implicite que personne ne maîtrise.

Accessibilité : le moment où « hint » ne veut pas dire « tooltip accessible »

Un popover ne remplace pas une réflexion ARIA. Un hint visuel, c’est souvent un role="tooltip" (ou une description associée) et une relation claire via aria-describedby. Si ton hint contient des actions, ce n’est déjà plus un hint, c’est un mini-popover interactif, et là tu dois être très carré sur le focus et la fermeture.

Mon expérience là-dessus est assez simple : si ton “hint” doit être lisible au clavier, stable à l’écran, et cohérent avec un lecteur d’écran, tu vas probablement écrire un peu de logique autour de l’API native. Ce n’est pas grave. Le grave, c’est de croire que popover=hint te donne automatiquement le comportement UX et a11y d’un composant tooltip mature.

Mon arbitrage « prod ou pas » : quand je le tente, quand je l’évite

Je tente popover=hint quand l’UI est simple, quand le hint est réellement non critique, et quand une fermeture un peu différente ne te casse pas un parcours. Typiquement une aide sur une landing, un petit glossaire, une explication qui ne contient pas d’actions, dans une page où tu n’as pas déjà trois overlays qui se marchent dessus.

Je l’évite quand je suis dans une app dense avec des overlays imbriqués, des menus à étages, des portails partout, ou un design system où je sais que le même composant va être réutilisé dans des contextes imprévisibles. Dans ces cas-là, je préfère un composant tooltip/popover géré par le design system (avec Floating UI ou une équivalence), ou bien popover="manual" avec des règles explicites, plutôt que de jouer à deviner comment la pile native va se comporter selon l’arbre DOM.

Conclusion : la feature est cool, mais teste-la comme un composant “à risque”

Le piège de popover=hint, c’est qu’il te donne une expérience “wow” en 30 secondes, puis te présente la facture le jour où tu l’imbriques dans une UI réelle. Ce n’est pas une raison pour l’ignorer. C’est une raison pour le traiter comme un composant sensible : tu le valides dans tes vrais écrans, tu verifies clavier et clic extérieur, et si tu vois des incohérences liées à l’imbrication, tu reprends la main au plus vite. Le natif, c’est bien. Le natif imprévisible, ça finit en patch JS de minuit.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !