Tooltip

A tooltip is a short label that appears next to an element when you hover or focus it. It names things that have no room for a visible label, like icon buttons.

import { Bold, Italic, Underline } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

export default function TooltipDemo() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Bold" />}>
          <Bold />
        </TooltipTrigger>
        <TooltipContent>Bold</TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Italic" />}>
          <Italic />
        </TooltipTrigger>
        <TooltipContent>Italic</TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Underline" />}>
          <Underline />
        </TooltipTrigger>
        <TooltipContent>Underline</TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger render={<Button />}>Save</TooltipTrigger>
        <TooltipContent className="flex items-center gap-2">
          Save changes
          <Kbd aria-label="Command">⌘</Kbd>
          <Kbd>S</Kbd>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  );
}

The code

The component is a single file, read from the repository when the site is built. Press Show code to see all of it.

import type { ComponentProps } from "react";
import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
import { cx } from "@/lib/cx";
import { popupMotion, popupSurface } from "@/lib/styles";
import type { WithClass } from "@/lib/types";

// Wrap part of the page (or all of it) in a TooltipProvider and tooltips open without their
// usual delay once one has opened, so moving along a toolbar feels quick.
export const TooltipProvider = BaseTooltip.Provider;
export const Tooltip = BaseTooltip.Root;

// Renders a button by default. To put a tooltip on your own element, such as a Button, pass it
// as render: <TooltipTrigger render={<Button />}>.
export const TooltipTrigger = BaseTooltip.Trigger;

// side, sideOffset and align place the tooltip against its trigger; every other prop goes to the
// popup itself.
export function TooltipContent({
  side = "top",
  sideOffset = 6,
  align,
  className,
  children,
  ...props
}: WithClass<ComponentProps<typeof BaseTooltip.Popup>> &
  Pick<ComponentProps<typeof BaseTooltip.Positioner>, "side" | "sideOffset" | "align">) {
  return (
    <BaseTooltip.Portal>
      <BaseTooltip.Positioner side={side} sideOffset={sideOffset} align={align} className="z-50">
        <BaseTooltip.Popup className={cx(popupSurface, popupMotion, "px-2 py-1 text-sm", className)} {...props}>
          {children}
        </BaseTooltip.Popup>
      </BaseTooltip.Positioner>
    </BaseTooltip.Portal>
  );
}

Props

PropTypeDefaultWhat it does
side"top" | "right" | "bottom" | "left""top"On TooltipContent. Which side of the trigger it opens on. It flips to the other side if there is no room.
align"start" | "center" | "end""center"On TooltipContent. How it lines up along that side.
sideOffsetnumber6On TooltipContent. The gap in pixels between the trigger and the tooltip.
delaynumber600On TooltipTrigger. Milliseconds to wait after hovering before it opens.
closeDelaynumber0On TooltipTrigger. Milliseconds to wait after leaving before it closes.
renderelement—On TooltipTrigger. Puts the tooltip on your own element, such as a Button, instead of a plain button.

Wrap the part of the page that has tooltips in a TooltipProvider. Every other prop goes to the Base UI Tooltip underneath.

The thinking

A tooltip adds one thing: a name. If it needs a sentence, a button or a link, use a Popover instead.

  • Built on Base UI: Base UI handles the hover delay, the keyboard focus, the collision-aware placement and the Escape key.
  • Provider: Inside a TooltipProvider, once one tooltip has opened the next ones open without the delay. Sliding along a toolbar feels quick instead of sticky.
  • Your own trigger: Pass render to put the tooltip on a Button, as the demo does. The button keeps its own look and behavior.

Interface design

  • Small: Small text, snug padding, the shared overlay surface and no arrow. The gap to the trigger is enough to show what it belongs to.
  • Shortcuts: A Kbd can sit after the label to show the shortcut, as in the Save example.
  • Above everything: It floats above the page and the sticky header.

Interaction

  • Hover: It opens after a short pause, so moving the pointer across a toolbar doesn't flash tooltips.
  • Focus: It opens at once when the trigger receives keyboard focus.
  • Dismiss: Escape, moving the pointer away, moving focus away or clicking the trigger closes it.
  • Motion: A 150ms fade and a small scale from the side it opens from. With reduced motion turned on it appears at once.

Empathy

  • Not the only label: A tooltip doesn't appear on touch screens. An icon-only button still needs its own aria-label, as the demo does.
  • Keyboard users see it: It opens on focus, not only on hover.
  • Don't hide what matters: Use a tooltip to repeat or sharpen a name, never to hold information people need to finish a task.