HextaUI

Tooltip

Una pista breve al pasar el cursor o enfocar con el teclado que se abre tras un breve reposo, cambia al instante entre vecinos y muestra atajos.

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

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

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 se abre tras un breve reposo sobre el trigger, o de inmediato cuando el trigger recibe el foco del teclado. Una vez que hay uno visible, los vecinos se abren al instante y sin animación, así que recorrer una barra de herramientas se siente como leer etiquetas y no como esperar a cada una.

Ese cambio instantáneo funciona entre tooltips que comparten un TooltipProvider. Envuelve tu app (o una barra de herramientas) en uno para compartir el retraso. Un tooltip sin provider sigue funcionando por sí solo con los mismos valores por defecto.

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

TooltipGroup
└── TooltipTrigger

Lados

Define side en TooltipContent. Los lados lógicos siguen la dirección de lectura, y el tooltip se voltea cuando no hay espacio.

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>
  )
}

Con una flecha

arrow añade un puntero que permanece sobre el trigger incluso cuando el tooltip se desplaza para ajustarse a la pantalla.

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>
  )
}

Atajo de teclado

Pon un Kbd o KbdGroup después de la etiqueta. Toma los colores del tooltip y se sitúa al final, mostrando ⌘ en dispositivos Apple y Ctrl en el resto.

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>
  )
}

Barra de herramientas

Reposa sobre un botón y luego desplázate por el grupo. Los tooltips bajo un mismo TooltipProvider se intercambian al instante mientras te mueves entre ellos.

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>
  )
}

Deshabilitado

Un botón deshabilitado aún puede explicar por qué con focusableWhenDisabled, que lo mantiene con hover y en el orden de tabulación. disabled en Tooltip desactiva el 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>
  )
}

Contenido largo

El texto se ajusta a un ancho cómodo y nunca sobrepasa el borde de la pantalla, ni siquiera con URLs largas.

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>
  )
}

Controlado

Pasa open y onOpenChange para controlar tú mismo el tooltip. El motivo te indica qué lo abrió o lo cerró.

"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, muchos triggers

Crea un handle con createTooltipHandle y pasa un payload desde cada trigger para compartir un único 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 derecha a izquierda

Dentro de dir="rtl", inline-end se abre a la izquierda y el atajo pasa al otro extremo.

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>
  )
}
KeyAcción
TabEnfocar el trigger con el teclado abre su tooltip de inmediato.
EscCierra el tooltip y mantiene el foco en el trigger.
EnterSpaceActiva el trigger y cierra el tooltip, para que no tape lo que ocurre a continuación.
  • Un tooltip es una pista visual, no una etiqueta. Dale a los triggers solo con icono un aria-label que coincida con el texto del tooltip.
  • En táctil no se abre nada, y un toque solo hace lo que hace el trigger. No pongas en un tooltip nada que la gente necesite; usa un popover para eso.
  • Los usuarios con puntero pueden moverse al tooltip para leerlo o seleccionarlo sin que se cierre.
  • Con movimiento reducido, el tooltip se desvanece sin escalar.

Construido sobre el tooltip de Base UI. Cada parte acepta las props de la primitiva que envuelve.

PropTipoPredeterminado
delayMilisegundos de reposo sobre un trigger antes de abrir.
number300
closeDelayMilisegundos antes de cerrar tras salir.
number0
timeoutCuánto tiempo después de que se cierre uno se abre el siguiente al instante.
number400
PropTipoPredeterminado
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open, details) => void–
onOpenChangeCompleteSe llama después de la animación de apertura o cierre.
(open) => void–
disabledDesactiva el tooltip.
booleanfalse
disableHoverablePopupSe cierra en cuanto el puntero sale del trigger.
booleanfalse
trackCursorAxis
"none" | "x" | "y" | "both""none"
handleDe createTooltipHandle.
TooltipHandle<Payload>–
children
ReactNode | ({ payload }) => ReactNode–
PropTipoPredeterminado
delaySobrescribe el retraso del provider para este trigger.
number–
closeDelay
number–
closeOnClick
booleantrue
disabledImpide que este trigger abra el tooltip. El elemento sigue habilitado.
booleanfalse
handle
TooltipHandle<Payload>–
payload
Payload–
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescripción
data-slot="tooltip-trigger"El trigger.
data-popup-openPresente mientras su tooltip está abierto.
data-trigger-disabledPresente cuando el trigger no puede abrir el tooltip.
PropTipoPredeterminado
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
portalPropsComo container o keepMounted.
TooltipPortal props–
AtributoDescripción
data-slot="tooltip-content"El popup.
data-slot="tooltip-positioner"El contenedor de posicionamiento alrededor del popup.
data-sideEl lado en el que se abrió tras voltearse.
data-alignLa alineación.
data-instant"delay", "focus" o "dismiss" cuando se abre o se cierra sin animación.
data-starting-stylePresente mientras se anima la entrada.
data-ending-stylePresente mientras se anima la salida.
--available-widthEspacio entre el trigger y el borde de la pantalla.
--transform-originEl punto desde el que escala el tooltip.

Lo renderiza arrow por ti. Úsalo directamente solo al componer tu propia parte de contenido.

AtributoDescripción
data-slot="tooltip-arrow"La flecha.
data-uncenteredPresente cuando no puede apuntar al centro del trigger.

Usado en bloques

Bloques que se construyen sobre Tooltip.