Headless / Overlay

Tooltip

Tooltip shows a short text label on hover or keyboard focus. It binds to the trigger through aria-describedby, so a screen reader announces the label alongside the trigger's name.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A compound component for descriptive text attached to a control. It opens after openDelay on hover or focus, closes after closeDelay when the pointer or focus leaves, and closes immediately on Escape. Tooltip.Content renders into a Portal with role="tooltip".

Tooltip holds text and only text. The moment the content needs a link or a button, the component is wrong — a hover affordance cannot be operated by keyboard or touch users. Reach for HoverCard or Popover instead.

Install

example.tsx
yarn add @usemotif/headless
example.tsx
import { Tooltip } from '@usemotif/headless';

Anatomy

example.tsx
<Tooltip.Root>
<Tooltip.Trigger>
  <IconButton aria-label="Save"><SaveIcon /></IconButton>
</Tooltip.Trigger>
<Tooltip.Content>Save (⌘S)</Tooltip.Content>
</Tooltip.Root>

API

Tooltip.Root

Tooltip.Root

stable
function Tooltip.Root(props: TooltipRootProps): JSX.Element
openDelaynumber= 500

Milliseconds to wait before showing on hover or focus.

closeDelaynumber= 200

Milliseconds to wait before hiding — a small grace period for the cursor.

placement"top" | "bottom" | "left" | "right"= "bottom"

Where the tooltip sits relative to the trigger.

childrenReactNode

The Trigger and Content parts.

Tooltip.Trigger

Tooltip.Trigger

stable
function Tooltip.Trigger(props: TooltipTriggerProps): JSX.Element
childrenReactElementrequired

A single element. Tooltip clones it to attach hover and focus handlers and — while open — `aria-describedby`.

Tooltip.Content

Tooltip.Content

stable
function Tooltip.Content(props: TooltipContentProps): JSX.Element | null
offsetnumber= 8

Pixel gap between the trigger and the tooltip.

styleCSSProperties

Inline style for the tooltip — positioning is applied on top.

childrenReactNode

The tooltip text.

Accessibility

The tooltip binds to the trigger with aria-describedby while open, so its text is announced as the trigger's description. Because the trigger must be focusable for the focus path to work, the element inside Tooltip.Trigger should be a real button, link, or input — not a <div>.

A tooltip is supplementary. It can clarify a control, but it cannot be the only place a piece of information lives — a touch user never sees it, and a screen-reader user hears it only as a description. An icon-only button still needs its own aria-label; the tooltip repeats that name for sighted pointer users, it does not replace it.

Examples

A label for an icon button:

example.tsx
<Tooltip.Root>
<Tooltip.Trigger>
  <IconButton aria-label="Archive"><ArchiveIcon /></IconButton>
</Tooltip.Trigger>
<Tooltip.Content style={{ padding: '6px 10px', background: '#1C1917', color: '#fff' }}>
  Archive
</Tooltip.Content>
</Tooltip.Root>

A keyboard-shortcut hint:

example.tsx
<Tooltip.Root placement="top">
<Tooltip.Trigger><Button>Save</Button></Tooltip.Trigger>
<Tooltip.Content>Save changes — ⌘S</Tooltip.Content>
</Tooltip.Root>