<!-- Protocore Design System — Tooltip -->
# Tooltip

- **Category:** Overlay (`overlay`)
- **Slug:** `overlay/tooltip`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Tooltip, TooltipProvider } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/overlay/tooltip

> A hover/focus tip on a dark panel that labels or briefly explains the element it points at.

## When to use it

Reach for a Tooltip to **label an icon-only control** or add a one-line clarification that the user can safely ignore. It appears on hover or focus, holds nothing interactive, and vanishes on the next move.

If the tip contains a link, button, or copy the user must read to proceed, it is not a Tooltip. Use a **[Popover](/overlay/popover)** for interactive floating content, a **[HoverCard](/overlay/hover-card)** for a rich hover preview of an entity, and **[HelpTip](/inputs/help-tip)** for a persistent question-mark affordance beside a form field. Never put anything essential — or anything only reachable by hover — inside a Tooltip.

## Props

### Tooltip

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `children` | `ReactNode` | yes | — | The trigger element the tip describes. Wrapped as the Radix trigger via `asChild`. |
| `className` | `string` | no | — | — |
| `content` | `ReactNode` | yes | — | The tip body. Anything renderable; kept short (single line ideal). |
| `defaultOpen` | `boolean` | no | — | Uncontrolled initial open state. |
| `delayDuration` | `number` | no | `300` | Open delay in ms (overrides the provider's). |
| `onOpenChange` | `((open: boolean) => void)` | no | — | Fires when the open state changes. |
| `open` | `boolean` | no | — | Controlled open state. |
| `side` | `enum` | no | `top` | Preferred side of the trigger to render on. |
| `sideOffset` | `number` | no | `6` | Pixel gap between trigger and tip. |
| `style` | `CSSProperties` | no | — | — |

### TooltipProvider

_No documented props._

## Examples

### Basics

Wrap any focusable element. `content` is the tip; `children` is the trigger, mounted via `asChild`. Icon-only triggers must still carry their own `aria-label`.

```tsx
import { Tooltip, IconButton } from "@protocore/pds";
import { RefreshCw } from "lucide-react";

export default function TooltipBasics() {
  return (
    <Tooltip content="Re-sync the ledger from the last checkpoint">
      <IconButton aria-label="Re-sync ledger">
        <RefreshCw size={16} />
      </IconButton>
    </Tooltip>
  );
}
```

### Placement

`side` picks the preferred edge; Radix flips it on collision. Keep `sideOffset` small — the tip should read as attached to the trigger.

```tsx
import { Tooltip, Button, HStack } from "@protocore/pds";

const sides = ["top", "right", "bottom", "left"] as const;

export default function TooltipSides() {
  return (
    <HStack gap={3}>
      {sides.map((side) => (
        <Tooltip key={side} side={side} content={`Renders on ${side}`}>
          <Button variant="secondary">{side}</Button>
        </Tooltip>
      ))}
    </HStack>
  );
}
```

## Do & don't

**Do**

- Label icon-only buttons so their action is discoverable.
- Keep content to a single short line — 280px is the ceiling.
- Rely on the built-in 300ms delay so tips don't flash during pointer travel.
- Wrap one focusable trigger so keyboard users get the tip too.

**Don't**

- Put links, buttons, or form controls inside — use a Popover.
- Hide information the user actually needs to complete a task.
- Attach a tip to a disabled element (it can't receive focus or hover).
- Stack paragraphs of prose into the panel — that's a HoverCard.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the trigger, which opens the tip after the delay. |
| `Esc` | Dismisses the open tip while keeping focus on the trigger. |

**Notes**

- Opens on pointer hover and on keyboard focus; closes on blur, pointer-leave, or Esc.
- Radix links the tip to its trigger via aria-describedby, so screen readers announce it.
- Mount one TooltipProvider near the app root to share a single open-delay / skip-delay timer across every tooltip.
- The tip is advisory only — the interface must remain fully usable without ever revealing it.

## Related

`popover`, `hover-card`, `help-tip`, `kbd`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
