# Tooltip

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

Icon button with tooltip

[Complete TSX file](/examples/tooltip/default.tsx)

```tsx
"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

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/tooltip
```

With npm: `npx shadcn@4.18.0 add @siteplane/tooltip`. [Registry JSON](/registry/tooltip.json) lists source files and dependencies.

For this page’s examples, also install:

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

## Usage

```tsx
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

```text
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.

Formatting buttons with grouped tooltips

[Complete TSX file](/examples/tooltip/grouped.tsx)

```tsx
"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.

Tooltips on each side

[Complete TSX file](/examples/tooltip/positions.tsx)

```tsx
"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.

Tooltip on a disabled button

[Complete TSX file](/examples/tooltip/disabled.tsx)

```tsx
"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](/docs/components/glass/) shows every component with a glass variant.

Glass tooltip

[Complete TSX file](/examples/tooltip/glass.tsx)

```tsx
"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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| TooltipProvider.delay | number | 700 | Initial hover delay in milliseconds. |
| TooltipProvider.timeout | number | 550 | Shared provider timing window in milliseconds. |
| TooltipPopup.side | Positioner side | top | Preferred side. |
| TooltipPopup.align | start \| center \| end | center | Alignment. |
| TooltipPopup.sideOffset | Positioner sideOffset | 4 | Gap. |
| TooltipPopup.anchor | Positioner anchor | trigger | Places 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.disableAnchorTracking | Positioner disableAnchorTracking | false | Stops 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.tone | neutral \| error | neutral | error fills the popup with the solid destructive color, for an error. The earlier names default and destructive still work as deprecated aliases. |
| TooltipPopup.variant | default \| glass | default | glass shows a tinted, blurred view of what lies behind the popup. tone applies to the default surface only. |
| TooltipPopup.arrow | boolean | false | Points an arrow at the anchor; the gap to its tip stays sideOffset. |
| TooltipPopup.interactive | boolean | true | false lets the pointer pass through, for a popup that only reports. |
| TooltipPopup.portalProps | TooltipPrimitive.Portal.Props | unset | Portal options. |
| open / defaultOpen | boolean | false | Visibility. |
| onOpenChange | (open, eventDetails) => void | unset | Visibility callback. |
| onOpenChangeComplete | (open) => void | unset | Completion 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](https://base-ui.com/react/components/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](/source/tooltip).

## 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.
