# Tooltip

> Tooltip: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/tooltip](https://askrjs.com/docs/components/tooltip)

Status: stable. Packages: @askrjs/ui/tooltip, @askrjs/themes/tooltip.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
import { Button, Tooltip, TooltipContent, TooltipTrigger } from '@askrjs/themes/components';

<Tooltip>
  <TooltipTrigger asChild><Button aria-label="Archive project">Archive</Button></TooltipTrigger>
  <TooltipContent>Move this project to the archive</TooltipContent>
</Tooltip>
```

## Published props

Generated from the TypeScript declarations shipped by the installed package.

### `TooltipContentAsChildProps`

Import from `@askrjs/ui/tooltip`.

- `align?: OverlayAlign | undefined;`
- `asChild: true;`
- `children: JSXElement;`
- `forceMount?: boolean | undefined;`
- `ref?: Ref<Element>;`
- `side?: OverlaySide | undefined;`
- `sideOffset?: number | undefined;`

### `TooltipContentOwnProps`

Import from `@askrjs/ui/tooltip`.

- `align?: OverlayAlign | undefined;`
- `forceMount?: boolean | undefined;`
- `side?: OverlaySide | undefined;`
- `sideOffset?: number | undefined;`

### `TooltipContentProps`

Import from `@askrjs/ui/tooltip`.

- `align?: OverlayAlign | undefined;`
- `asChild?: false | undefined;`
- `children?: unknown;`
- `forceMount?: boolean | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `side?: OverlaySide | undefined;`
- `sideOffset?: number | undefined;`

### `TooltipOwnProps`

Import from `@askrjs/ui/tooltip`.

- `children?: unknown;`
- `defaultOpen?: boolean | undefined;`
- `id?: string | undefined;`
- `onOpenChange?: ((open: boolean) => void) | undefined;`
- `open?: boolean | undefined;`

### `TooltipPortalProps`

Import from `@askrjs/ui/tooltip`.

- `children?: unknown;`

### `TooltipProps`

Import from `@askrjs/ui/tooltip`.

- `children?: unknown;`
- `defaultOpen?: boolean | undefined;`
- `id?: string | undefined;`
- `onOpenChange?: ((open: boolean) => void) | undefined;`
- `open?: boolean | undefined;`

### `TooltipTriggerAsChildProps`

Import from `@askrjs/ui/tooltip`.

- `asChild: true;`
- `children: JSXElement;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<Element>;`
- `type?: undefined;`

### `TooltipTriggerProps`

Import from `@askrjs/ui/tooltip`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<HTMLButtonElement>;`
- `type?: "button" | "submit" | "reset" | undefined;`

## Purpose

Tooltip shows a short hint tied to an element, opened by hover or keyboard focus rather than a click. It's the right choice for a one-line label or clarification an icon button needs; once the content grows into something a user has to read or interact with, reach for Popover or Hover Card instead. Because it opens on focus, it also doubles as an accessible-name fallback for icon-only triggers.

## Install and import

The headless behavior lives at `@askrjs/ui/tooltip`, exporting `Tooltip`, `TooltipTrigger`, `TooltipPortal`, and `TooltipContent`. Most apps won't import from there directly — pull the styled version from `@askrjs/themes/tooltip`, which wraps the same primitives with the default theme's positioning and animation CSS already applied.

## Live examples

A minimal tooltip is a `Tooltip` wrapping a `TooltipTrigger` and a `TooltipContent` inside `TooltipPortal`, with the trigger set to whatever element the hint describes — commonly an icon `Button`. Toggling `side` and `align` on `TooltipContent` is the fastest way to see how the positioning engine reacts to different anchor placements before wiring up real content.

## Anatomy

`Tooltip` is the state root; `TooltipTrigger` is the hover/focus target and takes the same props as a button-like element. `TooltipPortal` renders `TooltipContent` outside the normal DOM flow so it isn't clipped by an ancestor's `overflow: hidden`, and `TooltipContent` is the actual bubble, positioned relative to the trigger.

## State model

Openness is a plain boolean on the root: pass `open` and `onOpenChange` to drive it from outside, or let `defaultOpen` seed uncontrolled behavior and manage it internally. There's no separate state for hover versus focus — both funnel into the same `open` value, so you don't need to reconcile two code paths for pointer and keyboard users.

## Keyboard and accessibility

The content renders with `role="tooltip"` and the trigger gets `aria-describedby` pointing at it, per `TOOLTIP_A11Y_CONTRACT` — so screen readers announce the hint as a description of the trigger, not as a separate focusable region. Focusing the trigger with the keyboard opens it the same as hovering does, and moving focus away closes it; there's no dedicated close button or additional tab stop to manage.

## Styling and tokens

`TooltipContent` picks up `data-state`, `data-side`, and `data-align` attributes, which is what the default theme's CSS hooks into for entrance/exit transitions and directional arrow placement — `data-disabled` is set on `TooltipTrigger` instead, reflecting the trigger's disabled state, not the content. Stacking is controlled by the shared overlay z-index scale, where tooltips sit above toasts at `var(--ak-z-tooltip)` (`1600`, no CSS fallback value — the token is expected to always be defined by the loaded theme) so they're never hidden behind other overlay layers.

## API

`TooltipContent` accepts `forceMount`, `side`, `align`, and `sideOffset` on top of standard div props — `forceMount` keeps it in the DOM even when closed, which matters if you're animating exit with CSS rather than unmounting. `TooltipTrigger` takes the same prop surface as any button-like Askr primitive, including an `asChild`-style variant for rendering a custom trigger element.

## Edge cases

Tooltips don't fire from a touch tap the way hover-based UI generally doesn't on touch devices, so don't rely on tooltip content to convey anything essential — treat it as supplementary. If the trigger is disabled, `data-disabled` is still set for styling, but you're responsible for deciding whether a disabled control should still explain itself via tooltip. Rapid focus changes between adjacent triggers can also cause overlapping open/close transitions if your CSS doesn't account for the interrupted `data-state`.

## Related pages

See Popover for click-triggered, persistently-open content, and Hover Card for richer hover-triggered previews that can contain interactive elements. Dropdown and Menu cover the click-to-open, keyboard-navigable list pattern that Tooltip intentionally doesn't attempt.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/hover-card/index.md) | [Next](https://askrjs.com/docs/components/menu-dropdown-and-context-menu/index.md)
