Siteplane UI

Tooltip

Add a short description to a control on hover or focus.

Markdown
"use client";

import { CopyIcon } from "lucide-react";
import { Button } from "@/siteplane/components/button";
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from "@/siteplane/components/tooltip";

export default function Example() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger
          render={
            <Button aria-label="Copy link" size="icon-md" variant="outline" />
          }
        >
          <CopyIcon />
        </TooltipTrigger>
        <TooltipPopup>Copy link</TooltipPopup>
      </Tooltip>
    </TooltipProvider>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/tooltip

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/toggle-group

Usage

import { Button } from "@/siteplane/components/button";
import {
  TooltipProvider,
  Tooltip,
  TooltipTrigger,
  TooltipPopup,
} from "@/siteplane/components/tooltip";

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger render={<Button variant="outline" />}>Save</TooltipTrigger>
    <TooltipPopup>Save changes</TooltipPopup>
  </Tooltip>
</TooltipProvider>;

Composition

TooltipProvider
└── Tooltip
    ├── TooltipTrigger
    └── TooltipPopup / TooltipContent

Grouped tooltips

Wrap nearby tooltips in one TooltipProvider. After the first tooltip opens, moving to the next trigger shows its tooltip without the initial delay.

"use client";

import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import {
  ToggleGroup,
  ToggleGroupItem,
} from "@/siteplane/components/toggle-group";
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from "@/siteplane/components/tooltip";

const formats = [
  { value: "bold", label: "Bold", Icon: BoldIcon },
  { value: "italic", label: "Italic", Icon: ItalicIcon },
  { value: "underline", label: "Underline", Icon: UnderlineIcon },
];

export default function Example() {
  return (
    <TooltipProvider>
      <ToggleGroup
        aria-label="Text formatting"
        defaultValue={["bold"]}
        multiple
      >
        {formats.map(({ value, label, Icon }) => (
          <Tooltip key={value}>
            <TooltipTrigger
              render={<ToggleGroupItem aria-label={label} value={value} />}
            >
              <Icon />
            </TooltipTrigger>
            <TooltipPopup>{label}</TooltipPopup>
          </Tooltip>
        ))}
      </ToggleGroup>
    </TooltipProvider>
  );
}

Side

Use side to place the tooltip above (default), below or beside its trigger. It flips to the opposite side when there isn’t enough room.

"use client";

import { Button } from "@/siteplane/components/button";
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from "@/siteplane/components/tooltip";

const sides = [
  { side: "top", label: "Top" },
  { side: "right", label: "Right" },
  { side: "bottom", label: "Bottom" },
  { side: "left", label: "Left" },
] as const;

export default function Example() {
  return (
    <TooltipProvider>
      <div className="flex flex-wrap gap-2">
        {sides.map(({ side, label }) => (
          <Tooltip key={side}>
            <TooltipTrigger render={<Button variant="outline" />}>
              {label}
            </TooltipTrigger>
            <TooltipPopup side={side}>Add to favorites</TooltipPopup>
          </Tooltip>
        ))}
      </div>
    </TooltipProvider>
  );
}

Disabled button

Use aria-disabled instead of disabled so the button stays focusable and can still explain why it’s unavailable. Siteplane UI Button blocks activation while aria-disabled is set.

"use client";

import { Button } from "@/siteplane/components/button";
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from "@/siteplane/components/tooltip";

export default function Example() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger render={<Button aria-disabled="true" />}>
          Save
        </TooltipTrigger>
        <TooltipPopup>Make a change before saving</TooltipPopup>
      </Tooltip>
    </TooltipProvider>
  );
}

Glass

Set variant="glass" when the tooltip opens over a photo, a video or a color surface. The popup then shows a tinted, blurred view of what lies behind it. tone applies to the default surface only. Glass shows every component with a glass variant.

The gradient only makes the glass visible. It is not part of the component.

"use client";

import { CopyIcon } from "lucide-react";
import { Button } from "@/siteplane/components/button";
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from "@/siteplane/components/tooltip";

export default function Example() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger
          render={
            <Button aria-label="Copy link" shape="square" variant="glass" />
          }
        >
          <CopyIcon />
        </TooltipTrigger>
        <TooltipPopup variant="glass">Copy link</TooltipPopup>
      </Tooltip>
    </TooltipProvider>
  );
}

API reference

Component properties
PropTypeDefaultDescription
TooltipProvider.delaynumber700Initial hover delay in milliseconds.
TooltipProvider.timeoutnumber550Shared provider timing window in milliseconds.
TooltipPopup.sidePositioner sidetopPreferred side.
TooltipPopup.alignstart | center | endcenterAlignment.
TooltipPopup.sideOffsetPositioner sideOffset4Gap.
TooltipPopup.anchorPositioner anchortriggerPlaces the popup against an element, a ref, a virtual element or a function that returns one, instead of the trigger. A popup with an anchor of its own sizes to its content and takes no positioner transition, so whatever moves the anchor moves the popup directly.
TooltipPopup.disableAnchorTrackingPositioner disableAnchorTrackingfalseStops the popup from watching its anchor and itself for changes of size and place. Use it for an anchor that you move and pass again at every frame, such as a virtual element that trails the pointer.
TooltipPopup.toneneutral | errorneutralerror fills the popup with the solid destructive color, for an error. The earlier names default and destructive still work as deprecated aliases.
TooltipPopup.variantdefault | glassdefaultglass shows a tinted, blurred view of what lies behind the popup. tone applies to the default surface only.
TooltipPopup.arrowbooleanfalsePoints an arrow at the anchor; the gap to its tip stays sideOffset.
TooltipPopup.interactivebooleantruefalse lets the pointer pass through, for a popup that only reports.
TooltipPopup.portalPropsTooltipPrimitive.Portal.PropsunsetPortal options.
open / defaultOpenbooleanfalseVisibility.
onOpenChange(open, eventDetails) => voidunsetVisibility callback.
onOpenChangeComplete(open) => voidunsetCompletion of visibility motion.

Tooltip is a direct Root export. TooltipTrigger wraps Trigger; TooltipPopup includes Portal, Positioner and Viewport. TooltipContent aliases Popup. TooltipCreateHandle exposes detached-trigger handles; TooltipPrimitive is the Base UI Tooltip namespace. Other interaction and trigger props are inherited; styled Popup className is a string. There is no tooltip alignOffset or arbitrary positioner-props bag in this wrapper. Without an anchor of its own, the positioner keeps the size the popup opened with and glides when its place changes. The popup is at most 18rem (288 px) wide, so a long text wraps into a compact block and an unbroken word breaks; a max-w-* class in className sets another width.

Read the installed source.

Usage notes

Use a visible label or aria-label independently of the tooltip. Do not put interactive content in a tooltip or make essential instructions hover-only. Escape dismisses the tooltip. Use Popover for an interactive explanation.