Tooltip
Introduction
A component for displaying contextual information on hover or focus.
Usage
High-level wrapper
import { Tooltip } from "../components/ui/tooltip";
import { Button } from "../components/ui/button";
export default function MyPage() {
return (
<Tooltip content="This is the tooltip content" placement="bottom" showArrow asChild>
<Button>Hover me</Button>
</Tooltip>
);
}
Prefer asChild when the trigger is already a focusable element (a
Button, a link, …): it merges the tooltip's aria-describedby, hover/focus
listeners, and data-* attributes directly onto that element. Without
asChild, the trigger is wrapped in an extra <div tabindex="0"> — useful
for wrapping inert content (plain text, an icon) that isn't itself focusable,
but it adds a second, redundant tab stop when the child is already
interactive.
CMS Page Builder
This component is available as a tooltip block in the Page Builder (content/pages/*.json). The CMS trigger is a plain triggerText string, auto-wrapped in an outline Button with asChild (the same pattern Popover/HoverCard use):
{
"type": "tooltip",
"content": "Free cancellation up to 24 hours before check-in.",
"triggerText": "Cancellation Policy",
"placement": "top",
"showArrow": true
}
Props
Tooltip (High-level wrapper)
| Prop | Type | Description |
|---|---|---|
children | any | The element that triggers the tooltip. |
content | any | The content to display within the tooltip. |
showArrow | boolean | Whether to show an arrow pointing to the trigger. |
| placement | `"top" \ | "bottom" \ | "left" \ | "right"` | Which side of the trigger the content opens on. Default "top". Flips to the opposite side automatically if there isn't enough viewport room. |
| open | boolean | Whether the tooltip is open (controlled). |
| defaultOpen | boolean | Initial open state (uncontrolled). Default false. |
| onOpenChange | (details: { open: boolean }) => void | Called when the tooltip opens or closes. |
| openDelay | number | Delay (ms) before showing on hover. Default 100. |
| closeDelay | number | Delay (ms) before hiding on mouse-out. Default 100. |
| closeOnEscape | boolean | Close when Escape is pressed. Default true. |
| disabled | boolean | Whether the tooltip is disabled. |
| interactive | boolean | Forces hydration as an island. Defaults to true. |
| id | string | Unique identifier for the tooltip. |
| asChild | boolean | Whether to merge props onto the immediate child element instead of wrapping in a div. |
Hovering onto or focusing the trigger opens the tooltip; moving the pointer
onto the tooltip's own content (e.g. a link inside it) keeps it open, per
WCAG 1.4.13.
Focusing the trigger (keyboard navigation) opens it immediately, and blurring
closes it immediately — the openDelay/closeDelay only apply to hover.
Limitations
The interactive island positions and sizes the tooltip relative to its
trigger: it flips to the opposite side (e.g. top → bottom) when the
requested placement would overflow the viewport, and clamps the cross axis so
the content never renders off-screen. It does not track scroll containers or
resize observers the way Floating UI does — repositioning only re-runs when
the tooltip opens and on window resize.