Tooltip
A brief label that appears on hover or focus — most often naming an icon-only
control. Built on Base UI Tooltip. Tooltips are supplementary and visual-only:
never put essential or interactive content in one (use Popover for that).
Import
import { Tooltip } from "@photon-ai/pho-ui/components/tooltip";
Basic
Wrap your app (or a section) in a single Tooltip.Provider to share timing.
The trigger must be focusable — hover it or tab to it. Because the tooltip is
visual-only, give the trigger an aria-label that matches the popup text.
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger
render={
<Button svgOnly aria-label="Copy deployment URL">
<Copy />
</Button>
}
/>
<Tooltip.Portal>
<Tooltip.Positioner>
<Tooltip.Popup>Copy deployment URL</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
Placement
Tooltip.Positioner places the popup with side (top by default) and align
(center). It flips to the opposite side to avoid collisions. The pho
positioner sets a sideOffset of 6; override it or alignOffset for more gap.
<Tooltip.Positioner side="right">
<Tooltip.Popup>Copy deployment URL</Tooltip.Popup>
</Tooltip.Positioner>
Grouping
Tooltips under one Tooltip.Provider share a delay. Once one opens, moving to
an adjacent trigger within the provider’s timeout (400 ms) opens the next
instantly — so a toolbar of icon buttons feels responsive rather than making you
wait through the open delay each time.
<Tooltip.Provider delay={400}>
{formats.map(({ label, icon: Icon }) => (
<Tooltip.Root key={label}>
<Tooltip.Trigger
render={
<Button svgOnly aria-label={label}>
<Icon />
</Button>
}
/>
<Tooltip.Portal>
<Tooltip.Positioner>
<Tooltip.Popup>{label}</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
))}
</Tooltip.Provider>
Anatomy
- Tooltip.Provider — shares open/close delays across tooltips
- Tooltip.Root — owns a single tooltip’s open state
- Tooltip.Trigger — the focusable anchor
- Tooltip.Portal → Positioner → Popup — floating content
Props
Set on Tooltip.Provider:
- delay —
number— ms before a tooltip opens - closeDelay —
number— ms before a tooltip closes - timeout —
number(default400) — window for the next tooltip to open instantly
Set on Tooltip.Root:
- open —
boolean— controlled open state - defaultOpen —
boolean(defaultfalse) — uncontrolled initial open state - onOpenChange —
(open, eventDetails) => void— fires when the tooltip opens or closes - disabled —
boolean(defaultfalse) — prevent the tooltip from opening - trackCursorAxis —
none·x·y·both(defaultnone) — follow the cursor along an axis - disableHoverablePopup —
boolean(defaultfalse) — close when the pointer leaves the trigger instead of allowing hover onto the popup
Set on Tooltip.Trigger:
- delay —
number(default600) — ms before this trigger opens the tooltip - closeDelay —
number(default0) — ms before this trigger closes the tooltip - closeOnClick —
boolean(defaulttrue) — close when the trigger is clicked
Set on Tooltip.Positioner:
- side —
top·bottom·left·right·inline-start·inline-end(defaulttop) - align —
start·center·end(defaultcenter) - sideOffset —
number(default6in pho) — gap between trigger and popup - alignOffset —
number(default0) — offset along the alignment axis
Accessibility
Tooltips serve sighted mouse and keyboard users; they are not exposed to
touch or screen-reader users. Because of that, the tooltip text is not a
substitute for labelling the trigger — the trigger must have its own
aria-label (or visible text) that closely matches the popup, so screen-reader
users get the same information. If the content is essential or interactive, use
Popover with openOnHover instead.
The trigger renders a focusable <button> and gets data-popup-open while the
tooltip is showing.
Behavior:
- Opens on hover or keyboard focus; closes on blur or pointer leave
- Escape closes the open tooltip
- Opening waits out the delay; adjacent triggers in the same
Provideropen instantly within thetimeoutwindow
Best practices
- Use a tooltip to name an icon-only control or add a short hint — keep it to a few words, no links or buttons.
- Give the trigger an
aria-labelmatching the tooltip text; the tooltip alone won’t reach touch or screen-reader users. - The trigger must be focusable so keyboard users get the tooltip too; an icon button is ideal.
- Don’t hide critical information behind a tooltip; if losing it would block the
task, put it inline or in a
Popover.