# Tooltip (/ui/components/react/tooltip)



<Playground component="tooltip" />

## Usage [#usage]

```tsx
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@appica/ui-react/tooltip'
import { Button } from '@appica/ui-react/button'
```

```tsx
<TooltipProvider>
  <Tooltip>
    <TooltipTrigger
      render={
        <Button variant="outline" size="icon-md" aria-label="Add to favorites">
          <Heart />
        </Button>
      }
    />
    <TooltipContent>Add to favorites</TooltipContent>
  </Tooltip>
</TooltipProvider>
```

A tooltip is a short, **non-interactive hint** that appears when its trigger is hovered or focused — most often to name an icon-only control. The trigger is wired to the popup through `render`: pass it your own [`Button`](/ui/components/react/button) (or any focusable element) and the tooltip manages its description and open/close.

`Tooltip` is a compound component built on [Base UI's Tooltip](https://base-ui.com/react/components/tooltip):

* **`TooltipProvider`** — wraps a group of tooltips and shares the hover `delay`. Place one high in your app so that once one tooltip has opened, moving to a sibling shows its tooltip instantly instead of waiting out the delay again.
* **`Tooltip`** — the root for a single tooltip. Holds its open state; set `trackCursorAxis` to follow the pointer or `disabled` to suppress it.
* **`TooltipTrigger`** — the element the tooltip describes. Use `render` to project it onto your own control.
* **`TooltipContent`** — the portalled, auto-positioned popup. Draws the bubble and the optional `arrow`, and accepts the shared [floating props](/ui/components/react/popover) (`side`, `align`, `sideOffset`, …).

<Callout type="warning" title="Tooltips aren't reachable on touch or by screen readers">
  A tooltip only appears on hover/focus, so its text is invisible on touch devices and isn't announced as the control's
  name. Always give an icon-only trigger its own `aria-label` (or visible text) — the tooltip is a visual nicety on top,
  not the accessible name. For content that must be reachable by everyone, use a [`Popover`](/ui/components/react/popover).
</Callout>

Reach for a `Tooltip` to **label or hint at** a control. For rich or interactive floating content, use a [`Popover`](/ui/components/react/popover); for a list of actions, use a [`Dropdown Menu`](/ui/components/react/dropdown-menu); for a hover preview of a link, use a [`Preview Card`](/ui/components/react/preview-card).

## Examples [#examples]

### Side & alignment [#side--alignment]

Position the bubble with `side` (`top` / `right` / `bottom` / `left`) and `align` (`start` / `center` / `end`) on `TooltipContent`. It flips and shifts automatically to stay in view.

```tsx
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@appica/ui-react/tooltip'
import { Button } from '@appica/ui-react/button'

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

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

### Shared provider [#shared-provider]

Wrap a group of controls — here a [`Toolbar`](/ui/components/react/toolbar) of formatting buttons — in a single `TooltipProvider`. The first tooltip waits out the `delay`; once it's open, sweeping the pointer to a neighbour shows that tooltip immediately. Within that grace window the tooltip also skips its open/close animation, so it reads as one tooltip *relocating* across the toolbar rather than each popup re-bouncing.

```tsx
import * as React from 'react'
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@appica/ui-react/tooltip'
import { Toolbar, ToolbarButton, ToolbarSeparator } from '@appica/ui-react/toolbar'
import { Button } from '@appica/ui-react/button'
import { Bold, Italic, Underline, Strikethrough, Link } from '@appica/icons-react'

const ACTIONS = [
  { label: 'Bold', Icon: Bold },
  { label: 'Italic', Icon: Italic },
  { label: 'Underline', Icon: Underline },
  { label: 'Strikethrough', Icon: Strikethrough },
  { label: 'Link', Icon: Link },
] as const

export default function TooltipProviderDemo() {
  return (
    <TooltipProvider delay={400}>
      <Toolbar aria-label="Text formatting">
        {ACTIONS.map(({ label, Icon }, index) => (
          <React.Fragment key={label}>
            {index === ACTIONS.length - 1 && <ToolbarSeparator />}
            <Tooltip>
              <TooltipTrigger
                render={
                  <ToolbarButton
                    render={
                      <Button variant="ghost" size="icon-md" aria-label={label}>
                        <Icon />
                      </Button>
                    }
                  />
                }
              />
              <TooltipContent>{label}</TooltipContent>
            </Tooltip>
          </React.Fragment>
        ))}
      </Toolbar>
    </TooltipProvider>
  )
}
```

### Rich content [#rich-content]

`TooltipContent` accepts any markup, so you can pair a label with a [`Kbd`](/ui/components/react/kbd) shortcut. Keep it terse and non-interactive — the popup closes as soon as the pointer leaves the trigger.

```tsx
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@appica/ui-react/tooltip'
import { Button } from '@appica/ui-react/button'
import { Kbd } from '@appica/ui-react/kbd'
import { Bookmark } from '@appica/icons-react'

export default function TooltipShortcut() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger
          render={
            <Button variant="outline" size="icon-md" aria-label="Save">
              <Bookmark />
            </Button>
          }
        />
        <TooltipContent className="flex items-center gap-2">
          Save
          <Kbd size="sm">⌘ S</Kbd>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}
```

### Follow the cursor [#follow-the-cursor]

Set `trackCursorAxis` on the `Tooltip` to `'x'`, `'y'`, or `'both'` so the bubble follows the pointer along that axis — handy over a wide target like a chart or canvas where a fixed anchor would feel disconnected.

```tsx
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@appica/ui-react/tooltip'

export default function TooltipTrackCursor() {
  return (
    <TooltipProvider>
      <Tooltip trackCursorAxis="x">
        <TooltipTrigger
          render={
            <div className="bg-background-subtle border-border-strong flex h-32 w-72 items-center justify-center rounded-xl border border-dashed text-sm select-none">
              Hover and move across
            </div>
          }
        />
        <TooltipContent>Following the cursor</TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}
```

### Without an arrow [#without-an-arrow]

Pass `arrow={false}` to drop the pointer for a flatter bubble that sits flush against its offset.

```tsx
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@appica/ui-react/tooltip'
import { Button } from '@appica/ui-react/button'

export default function TooltipNoArrow() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline">No arrow</Button>} />
        <TooltipContent arrow={false}>Sits flush, without a pointer</TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware placement follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

`start`/`end` alignment mirrors with the document direction. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<Callout type="note" title="Scoped RTL needs the direction on the positioner">
  The popup portals to `<body>`, so its `start`/`end` alignment mirrors from the **document** direction (read from CSS,
  not from `DirectionProvider`) — automatic when `<html dir="rtl">`. If you instead scope RTL to a subtree of an LTR
  page, set it on the positioner so alignment still mirrors: `<TooltipContent positionerProps={{ dir: 'rtl' }}>`. See
  [Floating popups in a scoped RTL region](/ui/docs/react/rtl#floating-popups-in-a-scoped-rtl-region).
</Callout>

<RtlPreview component="tooltip" />

## API reference [#api-reference]

`Tooltip` wraps [Base UI's Tooltip](https://base-ui.com/react/components/tooltip). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here — the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.

### TooltipProvider [#tooltipprovider]

Shares the hover delay across the tooltips it wraps. Renders no DOM of its own.

| Prop         | <ColMinWidth width="120">Type</ColMinWidth> | Default | <ColMinWidth width="240">Description</ColMinWidth>                                  |
| ------------ | ------------------------------------------- | ------- | ----------------------------------------------------------------------------------- |
| `delay`      | `number`                                    | `200`   | Hover-open delay in ms applied to every tooltip inside.                             |
| `closeDelay` | `number`                                    | `0`     | Hover-close delay in ms.                                                            |
| `timeout`    | `number`                                    | `400`   | Window after a tooltip closes during which the next one opens instantly (no delay). |

### Tooltip [#tooltip]

The root for a single tooltip. Holds the open state; renders no DOM of its own.

| Prop              | <ColMinWidth width="200">Type</ColMinWidth> | Default  | <ColMinWidth width="220">Description</ColMinWidth>          |
| ----------------- | ------------------------------------------- | -------- | ----------------------------------------------------------- |
| `open`            | `boolean`                                   | —        | Controlled open state. Pair with `onOpenChange`.            |
| `defaultOpen`     | `boolean`                                   | `false`  | Uncontrolled initial open state.                            |
| `onOpenChange`    | <Code>(open, eventDetails) => void</Code>   | —        | Fires when the tooltip opens or closes.                     |
| `trackCursorAxis` | <Code>'none' \| 'x' \| 'y' \| 'both'</Code> | `'none'` | Make the bubble follow the cursor along the given axis.     |
| `disabled`        | `boolean`                                   | `false`  | Prevent the tooltip from opening.                           |
| `delay`           | `number`                                    | —        | Override the provider's hover-open delay for this tooltip.  |
| `closeDelay`      | `number`                                    | —        | Override the provider's hover-close delay for this tooltip. |

### TooltipTrigger [#tooltiptrigger]

The element the tooltip describes. Forwards Base UI's `Tooltip.Trigger`; use `render` to project it onto your own control.

| Prop        | <ColMinWidth width="220">Type</ColMinWidth>                 | Default | Description                                                |
| ----------- | ----------------------------------------------------------- | ------- | ---------------------------------------------------------- |
| `render`    | <Code>ReactElement \| (props, state) => ReactElement</Code> | —       | Render the trigger as your own element (e.g. a `Button`).  |
| `className` | `string`                                                    | —       | Extra classes on the trigger, merged via `tailwind-merge`. |

### TooltipContent [#tooltipcontent]

The portalled, auto-positioned bubble. Draws the popup and the optional arrow, and accepts the shared floating props. Defaults to `side="top"`, `align="center"`, and `sideOffset={8}` (or `4` without an arrow).

| Prop               | <ColMinWidth width="160">Type</ColMinWidth>         | Default    | Description                                                            |
| ------------------ | --------------------------------------------------- | ---------- | ---------------------------------------------------------------------- |
| `arrow`            | `boolean`                                           | `true`     | Render the pointer.                                                    |
| `side`             | <Code>'top' \| 'right' \| 'bottom' \| 'left'</Code> | `'top'`    | Preferred side to open on.                                             |
| `align`            | <Code>'start' \| 'center' \| 'end'</Code>           | `'center'` | Alignment along that side.                                             |
| `sideOffset`       | `number`                                            | `8`        | Gap between the trigger and the bubble.                                |
| `alignOffset`      | `number`                                            | `0`        | Shift along the alignment axis.                                        |
| `collisionPadding` | `number`                                            | `5`        | Minimum gap kept from the viewport edge when flipping/shifting.        |
| `anchor`           | <Code>Element \| ref \| VirtualElement</Code>       | —          | Anchor against a different element than the trigger.                   |
| `container`        | <Code>HTMLElement \| ref \| null</Code>             | —          | Portal target — render the popup into a different node.                |
| `positionerProps`  | `object`                                            | —          | Escape hatch for the positioner element (`style`, `render`, `dir`, …). |
| `portalProps`      | `object`                                            | —          | Escape hatch for the portal element (`style`, `render`, …).            |
| `className`        | `string`                                            | —          | Extra classes on the bubble.                                           |

## Accessibility [#accessibility]

* The popup is wired to the trigger via `aria-describedby`, so screen readers announce it as a **description** — not the control's name. Give icon-only triggers an `aria-label` (or visible text) of their own.
* The tooltip opens on hover **and** keyboard focus, and closes on **Escape**, on blur, or when the pointer leaves.
* Tooltips don't open on touch — there's no hover — so never hide essential information in one.
* Within a `TooltipProvider`, the `delay` is shared and subsequent tooltips open instantly within the `timeout` window.
* Enter/exit transitions are skipped when the user prefers reduced motion.
