HextaUI

Tooltip

Une courte infobulle au survol ou au focus clavier, qui s’ouvre après un bref arrêt, passe instantanément d’un voisin à l’autre et affiche les raccourcis.

import {
  IconArrowBackUp,
  IconArrowForwardUp,
  IconShare2,
  IconTrash,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Kbd } from "@/components/ui/kbd"
import { TooltipGroup, TooltipTrigger } from "@/components/ui/tooltip"

export function TooltipDemo() {
  return (
    <TooltipGroup>
      <div className="flex items-center gap-1">
        <TooltipTrigger
          content={
            <>
              Undo
              <Kbd keys="mod+z" />
            </>
          }
          render={<Button variant="ghost" size="icon" aria-label="Undo" />}
        >
          <IconArrowBackUp />
        </TooltipTrigger>
        <TooltipTrigger
          content={
            <>
              Redo
              <Kbd keys="mod+shift+z" />
            </>
          }
          render={<Button variant="ghost" size="icon" aria-label="Redo" />}
        >
          <IconArrowForwardUp />
        </TooltipTrigger>
        <TooltipTrigger
          content="Share"
          render={<Button variant="ghost" size="icon" aria-label="Share" />}
        >
          <IconShare2 />
        </TooltipTrigger>
        <TooltipTrigger
          content="Move to trash"
          render={
            <Button variant="ghost" size="icon" aria-label="Move to trash" />
          }
        >
          <IconTrash />
        </TooltipTrigger>
      </div>
    </TooltipGroup>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/tooltip.json

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"
<Tooltip>
  <TooltipTrigger render={<Button variant="ghost" size="icon" aria-label="Undo" />}>
    <IconArrowBackUp />
  </TooltipTrigger>
  <TooltipContent>Undo</TooltipContent>
</Tooltip>

Un tooltip s'ouvre après un court arrêt sur le déclencheur, ou aussitôt que le déclencheur reçoit le focus clavier. Une fois l'un affiché, ses voisins s'ouvrent instantanément et sans animation, si bien que parcourir une barre d'outils revient à lire des labels plutôt qu'à attendre chacun.

Ce changement instantané fonctionne entre les tooltips qui partagent un TooltipProvider. Enveloppez votre application (ou une barre d'outils) dans un provider pour partager le délai. Un tooltip sans provider fonctionne seul avec les mêmes valeurs par défaut.

<TooltipProvider>
  <App />
</TooltipProvider>
TooltipProvider
└── Tooltip
    ├── TooltipTrigger
    └── TooltipContent

TooltipGroup
└── TooltipTrigger

Côtés

Définissez side sur TooltipContent. Les côtés logiques suivent le sens de lecture, et le tooltip se retourne quand la place manque.

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

const sides = [
  { side: "top", cell: "col-start-2 row-start-1" },
  { side: "inline-start", cell: "col-start-1 row-start-2" },
  { side: "inline-end", cell: "col-start-3 row-start-2" },
  { side: "bottom", cell: "col-start-2 row-start-3" },
] as const

export function TooltipSides() {
  return (
    <TooltipProvider>
      <div className="grid grid-cols-3 grid-rows-3 place-items-center gap-2">
        {sides.map(({ side, cell }) => (
          <div key={side} className={cell}>
            <Tooltip>
              <TooltipTrigger render={<Button variant="outline" />}>
                {side}
              </TooltipTrigger>
              <TooltipContent side={side}>{side}</TooltipContent>
            </Tooltip>
          </div>
        ))}
      </div>
    </TooltipProvider>
  )
}

Avec une flèche

arrow ajoute un pointeur qui reste sur le déclencheur même quand le tooltip se décale pour tenir à l'écran.

import { IconBell } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function TooltipWithArrow() {
  return (
    <Tooltip>
      <TooltipTrigger
        render={
          <Button variant="outline" size="icon" aria-label="Notifications" />
        }
      >
        <IconBell />
      </TooltipTrigger>
      <TooltipContent arrow>Notifications</TooltipContent>
    </Tooltip>
  )
}

Raccourci clavier

Placez un Kbd ou un KbdGroup après le label. Il reprend les couleurs du tooltip et se place à la fin, en affichant ⌘ sur les appareils Apple et Ctrl ailleurs.

import { IconDeviceFloppy } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { KbdGroup } from "@/components/ui/kbd"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function TooltipShortcut() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline" />}>
          <IconDeviceFloppy data-icon="inline-start" />
          Save
        </TooltipTrigger>
        <TooltipContent>
          Save changes
          <KbdGroup keys="mod+s" size="sm" />
        </TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline" />}>
          Go to inbox
        </TooltipTrigger>
        <TooltipContent>
          Go to inbox
          <KbdGroup keys="g i" size="sm" />
        </TooltipContent>
      </Tooltip>
    </div>
  )
}

Barre d'outils

Arrêtez-vous sur un bouton, puis glissez le long du groupe. Les tooltips sous un même TooltipProvider s'échangent instantanément pendant que vous passez de l'un à l'autre.

import {
  IconBold,
  IconItalic,
  IconLink,
  IconStrikethrough,
  IconUnderline,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Kbd } from "@/components/ui/kbd"
import { TooltipGroup, TooltipTrigger } from "@/components/ui/tooltip"

const tools = [
  { label: "Bold", keys: "mod+b", icon: IconBold },
  { label: "Italic", keys: "mod+i", icon: IconItalic },
  { label: "Underline", keys: "mod+u", icon: IconUnderline },
  { label: "Strikethrough", keys: "mod+shift+x", icon: IconStrikethrough },
  { label: "Insert link", keys: "mod+k", icon: IconLink },
]

export function TooltipToolbar() {
  return (
    <TooltipGroup side="bottom">
      <ButtonGroup aria-label="Formatting">
        {tools.map(({ label, keys, icon: Icon }) => (
          <TooltipTrigger
            key={label}
            content={
              <>
                {label}
                <Kbd keys={keys} size="sm" />
              </>
            }
            render={<Button variant="outline" size="icon" aria-label={label} />}
          >
            <Icon />
          </TooltipTrigger>
        ))}
      </ButtonGroup>
    </TooltipGroup>
  )
}

Désactivé

Un bouton désactivé peut quand même expliquer pourquoi avec focusableWhenDisabled, qui le garde survolable et dans l'ordre de tabulation. disabled sur Tooltip désactive le tooltip.

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function TooltipDisabled() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Tooltip>
        <TooltipTrigger render={<Button disabled focusableWhenDisabled />}>
          Publish
        </TooltipTrigger>
        <TooltipContent>Add a title before publishing</TooltipContent>
      </Tooltip>
      <Tooltip disabled>
        <TooltipTrigger render={<Button variant="outline" />}>
          No tooltip
        </TooltipTrigger>
        <TooltipContent>You won’t see this</TooltipContent>
      </Tooltip>
    </div>
  )
}

Contenu long

Le texte passe à la ligne à une largeur confortable et ne dépasse jamais le bord de l'écran, même pour de longues URL.

import { IconInfoCircle } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function TooltipLongContent() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline" />}>
          <IconInfoCircle data-icon="inline-start" />
          Retention
        </TooltipTrigger>
        <TooltipContent>
          Deleted projects stay in the trash for 30 days. After that they’re
          removed for good, along with their deployments and logs.
        </TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline" />}>
          Webhook URL
        </TooltipTrigger>
        <TooltipContent>
          https://api.example.com/v1/hooks/8f3a2c91d7e64b0f9a1c5e2d7b8a4f60/deliveries
        </TooltipContent>
      </Tooltip>
    </div>
  )
}

Contrôlé

Passez open et onOpenChange pour piloter vous-même le tooltip. La raison indique ce qui l'a ouvert ou fermé.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function TooltipControlled() {
  const [open, setOpen] = React.useState(false)
  const [reason, setReason] = React.useState("none")

  return (
    <div className="flex flex-col items-center gap-3">
      <div className="flex items-center gap-2">
        <Tooltip
          open={open}
          onOpenChange={(next, details) => {
            setOpen(next)
            setReason(details.reason)
          }}
        >
          <TooltipTrigger render={<Button variant="outline" />}>
            Hover or focus me
          </TooltipTrigger>
          <TooltipContent>Controlled tooltip</TooltipContent>
        </Tooltip>
        <Button
          variant="ghost"
          onClick={() => {
            setOpen((value) => !value)
            setReason("button")
          }}
        >
          {open ? "Hide" : "Show"}
        </Button>
      </div>
      <p className="text-sm text-muted-foreground">
        Open: {String(open)} · last reason: {reason}
      </p>
    </div>
  )
}

Un tooltip, plusieurs déclencheurs

Créez un handle avec createTooltipHandle et passez un payload depuis chaque déclencheur pour partager un seul tooltip.

"use client"

import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
  createTooltipHandle,
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

type Person = { name: string; initials: string; status: string }

const people: Person[] = [
  { name: "Ada Lovelace", initials: "AL", status: "Online" },
  { name: "Alan Turing", initials: "AT", status: "In a meeting" },
  { name: "Grace Hopper", initials: "GH", status: "Away" },
]

const presence = createTooltipHandle<Person>()

export function TooltipDetached() {
  return (
    <TooltipProvider>
      <div className="flex items-center gap-2">
        {people.map((person) => (
          <TooltipTrigger
            key={person.name}
            handle={presence}
            payload={person}
            aria-label={person.name}
            render={
              <button className="rounded-full outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden" />
            }
          >
            <Avatar>
              <AvatarFallback>{person.initials}</AvatarFallback>
            </Avatar>
          </TooltipTrigger>
        ))}
        <Tooltip handle={presence}>
          {({ payload }) => (
            <TooltipContent side="bottom">
              {payload ? `${payload.name} · ${payload.status}` : null}
            </TooltipContent>
          )}
        </Tooltip>
      </div>
    </TooltipProvider>
  )
}

De droite à gauche

Dans dir="rtl", inline-end s'ouvre à gauche et le raccourci passe à l'autre extrémité.

import { IconBookmark, IconHeart, IconShare2 } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Kbd } from "@/components/ui/kbd"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

const actions = [
  { label: "أعجبني", icon: IconHeart },
  { label: "حفظ", icon: IconBookmark, keys: "mod+d" },
  { label: "مشاركة", icon: IconShare2 },
]

export function TooltipRtl() {
  return (
    <div dir="rtl">
      <TooltipProvider>
        <div className="flex items-center gap-1">
          {actions.map(({ label, icon: Icon, keys }) => (
            <Tooltip key={label}>
              <TooltipTrigger
                render={
                  <Button variant="ghost" size="icon" aria-label={label} />
                }
              >
                <Icon />
              </TooltipTrigger>
              <TooltipContent side="inline-end" arrow>
                {label}
                {keys && <Kbd keys={keys} size="sm" />}
              </TooltipContent>
            </Tooltip>
          ))}
        </div>
      </TooltipProvider>
    </div>
  )
}
ToucheAction
TabDonner le focus clavier au déclencheur ouvre aussitôt son tooltip.
EscFerme le tooltip et garde le focus sur le déclencheur.
EnterSpaceActive le déclencheur et ferme le tooltip, pour qu'il ne cache pas ce qui se passe ensuite.
  • Un tooltip est un indice visuel, pas un label. Donnez aux déclencheurs réduits à une icône un aria-label identique au texte du tooltip.
  • Rien ne s'ouvre au toucher, et un tap ne fait que ce que fait le déclencheur. Ne mettez rien d'indispensable dans un tooltip ; utilisez un popover pour cela.
  • Les utilisateurs de pointeur peuvent se déplacer sur le tooltip pour le lire ou le sélectionner sans qu'il se ferme.
  • Avec la réduction des animations, le tooltip apparaît en fondu sans changement d'échelle.

Construit sur le tooltip de Base UI. Chaque partie accepte les props de la primitive qu'elle enveloppe.

PropTypePar défaut
delayMillisecondes d'arrêt sur un déclencheur avant l'ouverture.
number300
closeDelayMillisecondes avant la fermeture après le départ.
number0
timeoutDurée après la fermeture d'un tooltip pendant laquelle le suivant s'ouvre instantanément.
number400
PropTypePar défaut
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open, details) => void–
onOpenChangeCompleteAppelé après l’animation d’ouverture ou de fermeture.
(open) => void–
disabledDésactive le tooltip.
booleanfalse
disableHoverablePopupSe ferme dès que le pointeur quitte le déclencheur.
booleanfalse
trackCursorAxis
"none" | "x" | "y" | "both""none"
handleIssu de createTooltipHandle.
TooltipHandle<Payload>–
children
ReactNode | ({ payload }) => ReactNode–
PropTypePar défaut
delayRemplace le délai du provider pour ce déclencheur.
number–
closeDelay
number–
closeOnClick
booleantrue
disabledEmpêche ce déclencheur d'ouvrir le tooltip. L'élément reste activé.
booleanfalse
handle
TooltipHandle<Payload>–
payload
Payload–
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="tooltip-trigger"Le déclencheur.
data-popup-openPrésent tant que son tooltip est ouvert.
data-trigger-disabledPrésent quand le déclencheur ne peut pas ouvrir le tooltip.
PropTypePar défaut
side
"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""top"
align
"start" | "center" | "end""center"
sideOffset
number | (data) => number6, or 8 with arrow
alignOffset
number0
arrow
booleanfalse
collisionPadding
number8
arrowPadding
number8
portalPropsComme container ou keepMounted.
TooltipPortal props–
AttributDescription
data-slot="tooltip-content"Le popup.
data-slot="tooltip-positioner"Le wrapper de positionnement autour de la popup.
data-sideLe côté sur lequel il s'est ouvert après retournement.
data-alignL'alignement.
data-instant"delay", "focus" ou "dismiss" quand il s'ouvre ou se ferme sans animation.
data-starting-stylePrésent pendant l’animation d’entrée.
data-ending-stylePrésent pendant l’animation de sortie.
--available-widthMarge entre le déclencheur et le bord de l'écran.
--transform-originLe point à partir duquel le tooltip change d'échelle.

Rendu pour vous par arrow. Ne l'utilisez directement que pour composer votre propre partie de contenu.

AttributDescription
data-slot="tooltip-arrow"La flèche.
data-uncenteredPrésent quand il ne peut pas pointer vers le centre du déclencheur.

Utilisé dans les blocks

Des blocks qui s’appuient sur Tooltip.