Une Approche Légère pour les Tooltips en React
Vous avez besoin d’un tooltip. Peut-être s’agit-il d’une indication pour un bouton icône, ou d’un contexte pour un libellé tronqué. Avant de vous tourner vers un framework UI complet ou une bibliothèque héritée comme Tippy.js, réfléchissez à la quantité de code dont vous avez réellement besoin.
Ce guide explore les patterns légers de tooltips en React—de l’approche native la plus simple aux hooks personnalisés minimaux, jusqu’aux bibliothèques de tooltips headless comme Floating UI. Chaque étape ajoute des fonctionnalités sans alourdir votre bundle.
Points Clés à Retenir
- L’attribut natif
titlefournit des tooltips sans coût mais manque de style et de support clavier - Les tooltips CSS uniquement offrent du style et des états de focus sans JavaScript, mais ne peuvent pas gérer les collisions avec le viewport
- Un hook personnalisé minimal donne un contrôle programmatique tout en gardant la taille du bundle réduite
- Floating UI fournit la détection de collision et la conscience du viewport pour environ 3 Ko
- Privilégiez toujours l’accessibilité en utilisant les attributs ARIA appropriés et ne placez jamais d’informations critiques uniquement dans les tooltips
Commencer avec l’Attribut Title Natif
L’attribut title intégré au navigateur est la base sans JavaScript :
<button title="Save your changes">💾</button>
Cela fonctionne, mais c’est limité. Le tooltip apparaît après un délai, vous ne pouvez pas le styliser, et il ne s’affiche pas au focus clavier. Néanmoins, pour des cas vraiment simples, cela ne coûte rien.
Tooltips React CSS Uniquement
Pour des tooltips stylisés sans logique JavaScript, le CSS gère les états de survol de base :
function IconButton({ label, children }) {
return (
<button className="tooltip-trigger" aria-describedby="tooltip-id">
{children}
<span className="tooltip" id="tooltip-id" role="tooltip">{label}</span>
</button>
)
}
.tooltip-trigger {
position: relative;
}
.tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
pointer-events: none;
transition: opacity 0.15s;
}
.tooltip-trigger:hover .tooltip,
.tooltip-trigger:focus .tooltip {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.tooltip {
transition: none;
}
}
Ce pattern CSS uniquement respecte prefers-reduced-motion et se déclenche à la fois au survol et au focus. Pour l’accessibilité des tooltips React, le role="tooltip" et l’aria-label sur le bouton fournissent un contexte pour les lecteurs d’écran.
La limitation ? Pas de détection de collision. Si le tooltip se trouve près d’un bord du viewport, il est coupé.
Un Hook Personnalisé Minimal
Lorsque vous avez besoin d’un contrôle programmatique sans bibliothèque, un petit hook fonctionne bien :
import { useState, useCallback } from 'react'
function useTooltip() {
const [isOpen, setIsOpen] = useState(false)
const triggerProps = {
onMouseEnter: useCallback(() => setIsOpen(true), []),
onMouseLeave: useCallback(() => setIsOpen(false), []),
onFocus: useCallback(() => setIsOpen(true), []),
onBlur: useCallback(() => setIsOpen(false), []),
}
return { isOpen, triggerProps }
}
Utilisation :
function TooltipButton({ hint, children }) {
const { isOpen, triggerProps } = useTooltip()
const id = `tooltip-${React.useId()}`
return (
<span style={{ position: 'relative', display: 'inline-block' }}>
<button {...triggerProps} aria-describedby={isOpen ? id : undefined}>
{children}
</button>
{isOpen && (
<span id={id} role="tooltip" className="tooltip">
{hint}
</span>
)}
</span>
)
}
La relation aria-describedby connecte le bouton à son tooltip pour les technologies d’assistance. C’est important : les tooltips doivent compléter, et non remplacer, les libellés accessibles. Ne placez jamais d’informations critiques uniquement dans un tooltip.
Discover how at OpenReplay.com.
Bibliothèques de Tooltips Headless : Floating UI
Lorsque vous avez besoin d’un positionnement approprié—détection de collision, retournement, décalage—Floating UI est le choix moderne. C’est le successeur de Popper.js et pèse environ 3 Ko.
Les tooltips React Floating UI vous donnent des primitives sans opinions sur le style :
import {
useFloating,
offset,
flip,
shift,
useHover,
useFocus,
useInteractions,
} from '@floating-ui/react'
import { useState } from 'react'
function Tooltip({ label, children }) {
const [isOpen, setIsOpen] = useState(false)
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [offset(6), flip(), shift()],
})
const hover = useHover(context)
const focus = useFocus(context)
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus])
return (
<>
<span ref={refs.setReference} {...getReferenceProps()}>
{children}
</span>
{isOpen && (
<div
ref={refs.setFloating}
style={floatingStyles}
role="tooltip"
{...getFloatingProps()}
>
{label}
</div>
)}
</>
)
}
Cela gère automatiquement les limites du viewport. Le middleware flip repositionne lorsque l’espace manque, tandis que shift maintient le tooltip visible le long de l’axe.
Pour la sécurité SSR, les hooks de Floating UI n’accèdent pas à window ou document pendant le rendu. Si vous construisez une solution personnalisée, protégez ces références dans useEffect ou useLayoutEffect.
Quand Utiliser les Portals
Si le parent de votre tooltip a overflow: hidden ou des contextes d’empilement complexes, rendez le tooltip dans un portal :
import { createPortal } from 'react-dom'
{isOpen && createPortal(
<div ref={refs.setFloating} style={floatingStyles} role="tooltip">
{label}
</div>,
document.body
)}
Cela échappe aux conteneurs de découpage. Floating UI gère le positionnement indépendamment de l’endroit où le tooltip est rendu dans le DOM.
Pourquoi Pas Tippy.js ?
Tippy.js et react-popper ont bien servi pendant des années, mais ils sont effectivement hérités. Floating UI offre le même moteur de positionnement avec une empreinte plus petite et une meilleure intégration React via les hooks. Pour les nouveaux projets, les bibliothèques de tooltips headless comme Floating UI ou Radix UI Tooltip sont le choix pratique.
Choisir Votre Approche
Adaptez la complexité au besoin :
titlenatif : Coût zéro, contrôle zéro- CSS uniquement : Stylisé, accessible, pas de logique de positionnement
- Hook personnalisé : Contrôle total, code minimal, positionnement manuel
- Floating UI : Détection de collision, conscience du viewport, ~3 Ko
Conclusion
Les tooltips n’ont pas besoin d’être lourds. Commencez simple, ajoutez des fonctionnalités lorsque le cas d’usage l’exige, et gardez l’accessibilité au centre de chaque implémentation. L’attribut natif title fonctionne pour les cas de base, les solutions CSS uniquement gèrent les besoins de style, et lorsque vous avez besoin de détection de collision, Floating UI livre sans l’encombrement des bibliothèques héritées.
FAQ
Utilisez aria-describedby pour connecter l'élément déclencheur au contenu du tooltip. Ajoutez role tooltip à l'élément tooltip lui-même. Gardez le texte du tooltip concis et supplémentaire. Ne placez jamais d'informations essentielles uniquement dans les tooltips car les utilisateurs qui dépendent des claviers ou des lecteurs d'écran peuvent manquer le contenu retardé ou uniquement au survol.
Les tooltips CSS uniquement manquent de détection de collision. Le tooltip se positionne par rapport à son parent sans vérifier les limites du viewport. Utilisez Floating UI avec les middlewares flip et shift pour repositionner automatiquement les tooltips lorsqu'ils seraient autrement coupés. Alternativement, rendez les tooltips dans un portal pour échapper aux conteneurs overflow hidden.
Floating UI est le choix recommandé pour les nouveaux projets. C'est le successeur de Popper.js qui alimentait Tippy.js et offre une taille de bundle plus petite d'environ 3 Ko avec une meilleure intégration React via les hooks. Tippy.js fonctionne toujours mais est considéré comme hérité et pèse plus lourd que les alternatives modernes.
Utilisez les portals lorsque les parents du tooltip ont overflow hidden, overflow scroll, ou des contextes d'empilement complexes qui causent un découpage. Les portals rendent le tooltip directement dans document.body échappant à toutes les restrictions de conteneur. Floating UI gère le positionnement correctement indépendamment de l'endroit où le tooltip apparaît dans l'arbre DOM.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.