Tooltip
An anchored React tooltip that opens on hover or keyboard focus after a delay, switches instantly while moving within a group, fades and scales in, flips at the viewport edge, and carries an arrow pointing at its trigger.
Add a one-line hint to icon buttons, abbreviations or anything that needs a short explanation. It opens after the pointer rests past the delay (700ms by default) and immediately on keyboard Tab focus; tooltips wrapped in a Tooltip.Provider share their timing — sweep along a toolbar and the next tooltip skips the delay and switches straight over. The bubble is portalled to body and positioned with position: fixed; when there is not enough room it flips to the opposite side and shifts along the cross axis to stay in the viewport, with the arrow always pointing at the trigger's centre. On touch devices it opens on long-press.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add tooltip.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<Tooltip/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/tooltip.en.jsonOr, once registries are configured in components.json, install it as @webberui/tooltip.
Usage
import { Tooltip } from "@/components/ui/tooltip";
// A single tooltip
<Tooltip content="Copy link">
<button type="button" aria-label="Copy link">…</button>
</Tooltip>
// Toolbar: fast switching within one Provider shows instantly
<Tooltip.Provider delay={700} skipDelay={300}>
<Tooltip content="Bold ⌘B">
<button type="button" aria-label="Bold">B</button>
</Tooltip>
<Tooltip content="Italic ⌘I" side="bottom">
<button type="button" aria-label="Italic">I</button>
</Tooltip>
</Tooltip.Provider>Props
| Prop | Type | Default | Description |
|---|---|---|---|
content | ReactNode | — | Tooltip content |
children | ReactElement | — | A single focusable trigger; events, the ref and aria-describedby are merged in via cloneElement |
side | "top" | "right" | "bottom" | "left" | "top" | Preferred side; flips to the opposite side when there is not enough room |
align | "start" | "center" | "end" | "center" | Cross-axis alignment |
sideOffset | number | 4 | Distance from the trigger (px) |
alignOffset | number | 0 | Cross-axis offset (px) |
collisionPadding | number | 8 | Minimum distance kept from the viewport edge (px) |
delay | number | 700 | Open delay (ms); when unset, uses the delay of Tooltip.Provider |
closeDelay | number | 0 | Close delay after the mouse leaves (ms) |
duration | number | 100 | Entry/exit animation duration (ms), also written to --wb-tooltip-duration |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Initial open state in uncontrolled mode |
onOpenChange | (open: boolean) => void | — | Called when the open state changes |
disabled | boolean | false | Disabled: never opens, and closes if already open |
showArrow | boolean | true | Whether to show the arrow pointing at the trigger |
className | string | — | Merged into the floating root (the dark bubble itself) |
Tooltip.Provider
| Field | Type | Default | Description |
|---|---|---|---|
delay | number | 700 | Default open delay for tooltips in the group (ms) |
skipDelay | number | 300 | Within this window (ms) after any tooltip closes, moving to another trigger in the group opens it instantly with no delay |
children | ReactNode | — | Group content |
How it works
- Open/close rules: pointer enter starts the delay timer, pointer leave cancels it or closes after
closeDelay; pressing the trigger with a mouse dismisses the tooltip; keyboard focus (:focus-visible) opens immediately and blur closes; Escape closes; scrolling the container that holds the trigger closes. - Touch: a 500ms long-press opens it and it closes 1500ms after the finger lifts; the click that follows the long-press is swallowed so the button is not triggered by accident.
- Positioning: the bubble stays
visibility: hiddenuntil first measured, so it never flashes in the top-left corner; size changes of the trigger or bubble, window resizes and scrolling of any container recompute it, throttled with rAF. The bubble carriesdata-side/data-align(the actual placement after flipping) for custom styling. - Animation: it fades in, scales from 0.95 to 1 and slides 8px out from the trigger's side;
transform-originfollows the actual placement so the scale grows out of the corner nearest the trigger. To retune the timing globally, override--wb-tooltip-durationin CSS. - The tooltip itself is
pointer-events: noneand not interactive; for content with links or buttons, use a popover-style component instead.
Accessibility
- The bubble has
role="tooltip"with anidfromuseId; while open, the trigger getsaria-describedbypointing at it (any existingaria-describedbyis kept) - Keyboard Tab focus opens it and Escape closes it; focus caused by a mouse click does not open it, so the tooltip does not stick around
- Icon-only trigger buttons should still carry their own
aria-label— the tooltip is supplementary, not a replacement for the accessible name - When the user's system has "reduce motion" enabled, only the fade remains (same duration), with no scaling or sliding
Popover
An anchored popover opened from a trigger: positioned by side and align with automatic collision flipping, it fades, scales and slides in from its side, with full focus handling and outside-click dismissal.
Context Menu
A right-click menu that opens at the pointer, with delayed submenus, checkbox and radio items and shortcut hints, automatic edge flipping, and long-press support on touch.