Tooltip
Tooltip: anatomy, keyboard behavior, state, and theming in Askr.
Example
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>Reveal supporting text from an accessible trigger.
Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
TooltipContentAsChildProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
TooltipContentOwnProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
TooltipContentProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
TooltipOwnProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
defaultOpen | defaultOpen?: boolean | undefined; | — | — |
id | id?: string | undefined; | — | — |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | — |
open | open?: boolean | undefined; | — | — |
TooltipPortalProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
TooltipProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
defaultOpen | defaultOpen?: boolean | undefined; | — | — |
id | id?: string | undefined; | — | — |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | — |
open | open?: boolean | undefined; | — | — |
TooltipTriggerAsChildProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
type | type?: undefined; | — | — |
TooltipTriggerProps
Import from @askrjs/ui/tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
type | 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.