Tooltip
A small label that appears on hover or keyboard focus. It follows WCAG 1.4.13:
the tooltip is hoverable (you can move the pointer onto it), dismissible with
Escape, and persistent (it stays until you leave or press Escape). Tooltips are
not shown for touch input, which cannot hover; do not put essential information
only in a tooltip. It ships unstyled: style it through the data-state,
data-side, and data-align contract.
Demo
Usage
import { Tooltip } from '@hono-preact/ui';
export function SaveHint() {
return (
<Tooltip.Root>
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
<Tooltip.Positioner>
<Tooltip.Popup>
<Tooltip.Arrow />
Saved to your library
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Root>
);
}
The trigger opens after a short delay on hover and immediately on focus. Set
delay and closeDelay on Tooltip.Root to tune the timing.
Once open, the tooltip stays open while the pointer rests on the trigger, the
popup, or the safe corridor across the gap between them; it closes closeDelay
after the pointer leaves that region, and a move back inside cancels the close.
See useSafeArea.
Styling
Parts expose data-state="open" | "closed"; the Positioner and Arrow also
expose data-side and data-align. Size, z-index, and entry animation go on the
Popup inside the Positioner. The demo above uses the styles below; copy a
starting point in either flavor:
.docs-tooltip-positioner {
z-index: 50;
}
.docs-tooltip {
padding: 0.375rem 0.625rem;
border-radius: 0.375rem;
background: #18181b;
color: #fafafa;
font-size: 0.8125rem;
box-shadow: 0 6px 18px rgb(0 0 0 / 0.18);
opacity: 1;
transition: opacity 100ms ease;
}
@starting-style {
.docs-tooltip[data-state='open'] {
opacity: 0;
}
}
.docs-tooltip[data-state='closed'] {
animation: docs-tooltip-out 100ms ease-in forwards;
}
@keyframes docs-tooltip-out {
to {
opacity: 0;
}
}
/* A solid rotated square, same color as the chip, straddling the edge facing
the anchor. */
.docs-tooltip__arrow {
width: 8px;
height: 8px;
rotate: 45deg;
background: #18181b;
}
.docs-tooltip__arrow[data-side='bottom'] {
top: -4px;
}
.docs-tooltip__arrow[data-side='top'] {
bottom: -4px;
}
.docs-tooltip__arrow[data-side='right'] {
left: -4px;
}
.docs-tooltip__arrow[data-side='left'] {
right: -4px;
}
@media (prefers-color-scheme: dark) {
.docs-tooltip {
background: #27272a;
color: #fafafa;
}
.docs-tooltip__arrow {
background: #27272a;
}
}
@media (prefers-reduced-motion: reduce) {
.docs-tooltip {
transition: none;
}
}API reference
Every part accepts a render prop for composition (see
useRender) and forwards unknown props to the
element it renders. The Trigger, Positioner, Popup, and Arrow also expose
data-state="open" | "closed" and pass state to a render function.
Tooltip.Root
Provides open state, ids, refs, timing, and placement config to the parts. Renders only its children.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled open state. Pair with onOpenChange. |
defaultOpen | boolean | false | Initial open state when uncontrolled. |
onOpenChange | (open: boolean) => void | - | Called when the tooltip requests an open or close. |
delay | number | 600 | Open delay in ms after the pointer enters the trigger. |
closeDelay | number | 300 | Grace in ms before closing once the pointer leaves the trigger, popup, or safe corridor. |
side | 'top'|'right'|'bottom'|'left' | 'top' | Preferred side of the anchor to place the popup. |
align | 'start'|'center'|'end' | 'center' | Alignment along that side. |
offset | number | 8 | Gap in pixels between the anchor and the popup. |
children | ComponentChildren | - | The trigger and positioner. |
Tooltip.Trigger
The element the tooltip describes. Binds hover and focus, wires
aria-describedby, and anchors positioning. Default element
<button type="button">; use render to attach it to your own control.
| Prop | Type | Default | Description |
|---|---|---|---|
render | RenderProp<{ open: boolean }> | - | Compose or replace the element. |
children | ComponentChildren | - | Trigger content. |
...props | JSX.HTMLAttributes<HTMLButtonElement> | - | Forwarded; passed pointer / focus handlers run first. |
Sets data-state and aria-describedby (the popup id, only while open). Opens
after delay on a mouse pointerenter, immediately on focus; once open, it
closes closeDelay after the pointer leaves the trigger, popup, and safe
corridor, immediately on blur, or on Escape. A touch pointer does not open it.
Tooltip.Positioner
The fixed-positioned wrapper that Floating UI drives. Renders nothing until the
tooltip is open (mount on open). Default element <div>.
| Prop | Type | Default | Description |
|---|---|---|---|
render | RenderProp<{ side: Side; align: Align }> | - | Compose or replace the element. |
children | ComponentChildren | - | The popup (and optional arrow). |
...props | JSX.HTMLAttributes<HTMLDivElement> | - | Forwarded to the element. Style positioning via class. |
Sets position: fixed and the resolved data-side / data-align. The element
is promoted to the top layer via the Popover API, so it escapes ancestor clipping.
Tooltip.Popup
The tooltip surface. Default element <div> with role="tooltip".
| Prop | Type | Default | Description |
|---|---|---|---|
render | RenderProp<{ open: boolean }> | - | Compose or replace the element. |
children | ComponentChildren | - | Tooltip content (and optional arrow). |
...props | JSX.HTMLAttributes<HTMLDivElement> | - | Forwarded to the element. |
Sets role="tooltip", id, and data-state. Hoverable: moving the pointer
onto the popup cancels the close so the content stays readable.
Tooltip.Arrow
Optional pointer positioned from the Floating UI arrow data. Default element
<div>. Place it on the correct edge with CSS keyed on data-side.
| Prop | Type | Default | Description |
|---|---|---|---|
render | RenderProp<{ side: Side }> | - | Compose or replace the element. |
children | ComponentChildren | - | Optional arrow content. |
...props | JSX.HTMLAttributes<HTMLDivElement> | - | Forwarded to the element. |
Sets data-side and position: absolute with the computed offset.
Primitives
@hono-preact/ui exports the building blocks the parts use, for composing your
own components. Several have their own page with examples:
usePosition,
useDismiss,
useRender,
useControllableState,
mergeRefs, and
useSafeArea.
Accessibility
The tooltip is wired to its trigger through aria-describedby while open, so a
screen reader announces the content when the trigger receives focus. The popup
has role="tooltip".
- Both hover and keyboard focus open the tooltip, so it is reachable without a pointer.
- It is hoverable: moving the pointer from the trigger onto the popup keeps it open (WCAG 1.4.13), so content that needs to be read or selected does not vanish.
- It is dismissible with Escape and persistent: while the pointer rests on the trigger, the popup, or the safe corridor between them it never auto-hides on a timer; it closes once focus or the pointer leaves.
- Touch input cannot hover, so the tooltip is suppressed there. Do not place information that is only available through a tooltip; keep it supplementary.