# Tooltip

A short label that appears when the pointer rests on, or keyboard focus reaches, an element.

- **Import:** `import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@booleanpress/ui/tooltip"`
- **Radix Tooltip:** <https://www.radix-ui.com/primitives/docs/components/tooltip>
- **APG Tooltip:** <https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/>
- **Page:** <https://ui.booleanpress.com/components/tooltip> · @booleanpress/ui 0.1.0

## Usage

A tooltip adds a few words to an element that already has a name, most often an icon-only button. It carries no interactive content and never holds information that is needed to use the page: touch screens never show it.

```tsx
import { Button } from "@booleanpress/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@booleanpress/ui/tooltip"

export function SendTest() {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <Button variant="outline">Send test email</Button>
      </TooltipTrigger>
      <TooltipContent>Sends to your own address</TooltipContent>
    </Tooltip>
  )
}
```

`BooleanUIProvider` renders the tooltip provider at the root, so `Tooltip` works anywhere inside it. Its timing comes from the provider: `tooltipDelay` (500 ms by default) before the first tooltip opens, and `tooltipSkipDelay` (0 by default) for the next one, so sweeping the pointer across a row of icon buttons does not make tooltips flash. Use `TooltipProvider` only for a subtree that needs other timing; it takes `delayDuration` and `skipDelayDuration`. `side` and `align` on `TooltipContent` place it.

## Examples

### Basic

A tooltip on a labelled button.

```tsx
import { Button } from "@booleanpress/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@booleanpress/ui/tooltip"

export default function TooltipBasic() {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <Button variant="outline">Send test email</Button>
      </TooltipTrigger>
      <TooltipContent>Sends to your own address</TooltipContent>
    </Tooltip>
  )
}
```

### Icon row

Sweep the pointer across the row: each tooltip waits 500 ms, and none flashes.

```tsx
import { CopyIcon, PencilIcon, TrashIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@booleanpress/ui/tooltip"

const ACTIONS = [
  { label: "Copy the key", icon: CopyIcon },
  { label: "Rename the key", icon: PencilIcon },
  { label: "Delete the key", icon: TrashIcon },
]

export default function TooltipIconRow() {
  return (
    <div className="flex gap-1">
      {ACTIONS.map(({ label, icon: Icon }) => (
        <Tooltip key={label}>
          <TooltipTrigger asChild>
            <Button variant="ghost" size="icon-sm" aria-label={label}>
              <Icon />
            </Button>
          </TooltipTrigger>
          <TooltipContent>{label}</TooltipContent>
        </Tooltip>
      ))}
    </div>
  )
}
```

### Sides

`side` on each of the four sides.

```tsx
import { Button } from "@booleanpress/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@booleanpress/ui/tooltip"

const SIDES = ["top", "right", "bottom", "left"] as const

export default function TooltipSides() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      {SIDES.map((side) => (
        <Tooltip key={side}>
          <TooltipTrigger asChild>
            <Button variant="outline" className="capitalize">
              {side}
            </Button>
          </TooltipTrigger>
          <TooltipContent side={side}>On the {side}</TooltipContent>
        </Tooltip>
      ))}
    </div>
  )
}
```

### Disabled trigger

A disabled button gets no pointer or focus events, so a focusable wrapper carries the tooltip.

```tsx
import { Button } from "@booleanpress/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@booleanpress/ui/tooltip"

export default function TooltipDisabledTrigger() {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <span tabIndex={0} className="inline-flex rounded-md">
          <Button disabled className="pointer-events-none">
            Send email
          </Button>
        </span>
      </TooltipTrigger>
      <TooltipContent>Connect a mailer first</TooltipContent>
    </Tooltip>
  )
}
```

## Accessibility

**Semantics.** The content is a `div` with `role="tooltip"`. While it is open, the trigger has `aria-describedby` pointing at it, so a screen reader reads the text as the trigger's description.

**Labels.** A tooltip describes; it does not name. An icon-only button still needs its own `aria-label`, and the tooltip repeats the same words for sighted users.

**Focus.** The tooltip is never focusable. It opens when its trigger receives keyboard focus, at once and without the delay, and closes when focus leaves.

**Known limits.**

- Touch screens do not open it. Never put the only copy of a fact in a tooltip.
- Its content must be plain text: a person cannot move into it.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Focusing the trigger opens its tooltip at once; moving focus away closes it. |
| Escape | Closes the tooltip and leaves focus on the trigger. |

## API

### Tooltip

Renders Radix Tooltip.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` |  | Whether it starts open, when it controls itself. |
| `delayDuration` | `number` |  | The duration from when the pointer enters the trigger until the tooltip gets opened. This will override the prop with the same name passed to Provider. |
| `disableHoverableContent` | `boolean` |  | When `true`, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger. |
| `onOpenChange` | `((open: boolean) => void)` |  | Called with `true` or `false` when it opens or closes. |
| `open` | `boolean` |  | Whether it is open, when you control it. Pair it with `onOpenChange`. |

### TooltipTrigger

Renders Radix Tooltip.Trigger and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |

### TooltipContent

Renders Radix Tooltip.Content and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"center" \| "start" \| "end"` |  | Alignment along that side: `start`, `center` or `end`. `center` by default. |
| `alignOffset` | `number` |  |  |
| `aria-label` | `string` |  | A more descriptive label for accessibility purpose |
| `arrowPadding` | `number` |  |  |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |
| `avoidCollisions` | `boolean` |  |  |
| `collisionBoundary` | `Boundary \| Boundary[]` |  |  |
| `collisionPadding` | `number \| Partial<Record<"left" \| "right" \| "top" \| "bottom", number>>` |  |  |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
| `hideWhenDetached` | `boolean` |  |  |
| `onEscapeKeyDown` | `((event: KeyboardEvent) => void)` |  | Event handler called when the escape key is down. Can be prevented. |
| `onPointerDownOutside` | `((event: PointerDownOutsideEvent) => void)` |  | Event handler called when the a `pointerdown` event happens outside of the `Tooltip`. Can be prevented. |
| `side` | `"left" \| "right" \| "top" \| "bottom"` |  | The preferred side: `top`, `right`, `bottom` or `left`. `top` by default; it flips when there is no room. |
| `sideOffset` | `number` | `4` | Distance in pixels from the trigger. 0 by default. |
| `sticky` | `"partial" \| "always"` |  |  |
| `updatePositionStrategy` | `"always" \| "optimized"` |  |  |

### TooltipProvider

Renders Radix Tooltip.Provider and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `delayDuration` | `number` |  | The duration from when the pointer enters the trigger until the tooltip gets opened. This will override the prop with the same name passed to Provider. |
| `disableHoverableContent` | `boolean` |  | When `true`, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger. |
| `skipDelayDuration` | `number` |  | How much time a user has to enter another trigger without incurring a delay again. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="tooltip"` (Tooltip), `data-slot="tooltip-trigger"` (TooltipTrigger), `data-slot="tooltip-content"` (TooltipContent), `data-slot="tooltip-provider"` (TooltipProvider).

## Theming

The tooltip is inverted: `--foreground` as its fill and `--background` as its text, with a small arrow.

| Token | Used for |
| --- | --- |
| `--background` | text |
| `--foreground` | background, fill |
