Tooltip
Primitive

Tooltip

Supplemental text disclosure for hover, focus, and long-press interactions.

Live behavior

Tooltip in motion

Interactive
Atom behavior · App-owned appearance
?

Focus or hover the trigger to reveal supplemental text.

waiting for input

When to Use

Use Tooltip for a short text hint that explains a control without requiring interaction, especially an unfamiliar icon. Use visible text when the information is important, HoverCard for a richer preview, and Popover when the floating content contains buttons, links, or other controls.

Features

The behavior Atom owns before your product adds appearance.

  • Provider-level delay configuration.
  • Controlled and uncontrolled open state.
  • Opens on pointer hover, keyboard focus-visible, and a stationary 700 ms touch long press.
  • Cancels touch opening on early release, movement beyond 10 CSS pixels, scrolling, a second touch, touchcancel, disabling, or Trigger unmount.
  • Starts finite touch dismissal after release: 1500 ms for plain and 3000 ms for rich.
  • Floating UI positioning that preserves centered alignment by shifting within the viewport first. Edge-aligned content resolves alternate alignments before shifting, and both modes retain opposite- and perpendicular-side fallbacks; Arrow coordinates follow the resolved result.
  • aria-describedby wiring between trigger and tooltip content.
  • Portal and arrow parts.

Import

tsx
import { Tooltip } from "@flowstack-ui/atom";

Anatomy

tsx
<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Content>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

API Reference

Provider

Shares open, close, and skip-delay timing between descendant tooltips. Provider renders no DOM element.

PropTypeDefault
childrenReactNoderequired
openDelaynumber400
closeDelaynumber150
skipDelaynumber300

ARIA: Provider renders no element and adds no ARIA attributes.

Data attributes: Provider renders no element and exposes none.

Root

Owns one tooltip's open state, timers, Escape dismissal, touch state, and plain or rich hover behavior. Root renders no DOM element.

PropTypeDefault
childrenReactNoderequired
openboolean-
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void-
openDelaynumberProvider value or 400
closeDelaynumberProvider value or 150
disabledbooleanfalse
variant"plain" | "rich""plain"

ARIA: Root renders no element and adds no ARIA attributes.

Data attributes: Root renders no element and exposes none.

Trigger

Reference element that describes itself with tooltip content while open.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-describedbyContent ID while the tooltip is open
Data attributeValues
[data-slot]"tooltip-trigger"

Portal

Moves Content to another DOM container without rendering a wrapper.

PropTypeDefault
containerHTMLElement | nulldocument.body after mount
disabledbooleanfalse

ARIA: Portal adds no ARIA attributes.

Data attributes: Portal renders no wrapper and exposes none.

Content

Renders the positioned tooltip text and keeps Content available while the pointer moves from Trigger into Content. Content exposes measured --atom-floating-available-width and --atom-floating-available-height properties. Portalled Content preserves an explicit dir; otherwise it resolves direction from Trigger and then Direction.Provider.

PropTypeDefault
side"top" | "right" | "bottom" | "left""top"
align"start" | "center" | "end""center"
sideOffsetnumber4
ariaLabelstring-
onMouseEnterMouseEventHandler<HTMLDivElement>-
onMouseLeaveMouseEventHandler<HTMLDivElement>-
ARIA attributeValues
role"tooltip"
aria-labelValue from ariaLabel when provided
Data attributeValues
[data-slot]"tooltip"
[data-state]"open" | "closed"
[data-side]"top" | "right" | "bottom" | "left"
[data-variant]"plain" | "rich"
[data-positioned]Present after the first positioning frame

Plain Content is normally one short description. Rich Content may use a short title, supporting description, and non-interactive inline formatting. Both variants retain role="tooltip"; neither may contain links, buttons, inputs, or other focusable controls. Use HoverCard for a larger non-interactive preview and Popover for an interactive surface.

Arrow

Renders a decorative SVG pointer using the final side chosen after collision handling. getTooltipArrowGeometry exposes the same geometry for custom arrows.

PropTypeDefault
widthnumber10
heightnumber5
asChildbooleanfalse
renderRenderProp-

ARIA: Arrow is decorative and hidden from assistive technology.

Data attributeValues
[data-slot]"tooltip-arrow"
[data-side]"top" | "right" | "bottom" | "left"

Advanced compound parts can read the Provider, Root, and Content contexts with their public hooks and providers. getTooltipArrowGeometry returns the same SVG geometry used by Arrow.

Examples

Basic Tooltip

tsx
import { Tooltip } from "@flowstack-ui/atom";

export default function SaveTooltip() {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <button type="button">Save</button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content>Save changes</Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

Custom Trigger

tsx
import { Tooltip } from "@flowstack-ui/atom";

export default function HelpTooltip() {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <button type="button">Help</button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content side="right">More information</Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

Rich, Non-Interactive Tooltip

tsx
import { Tooltip } from "@flowstack-ui/atom";

export default function SearchTooltip() {
  return (
    <Tooltip.Provider>
      <Tooltip.Root variant="rich">
        <Tooltip.Trigger asChild>
          <button type="button" aria-label="Search workspace">Search</button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content>
            <strong>Workspace search</strong>
            <span>Search projects, files, and commands.</span>
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

Accessibility

Tooltip follows the WAI-ARIA tooltip pattern. Content is referenced by aria-describedby while open and must remain non-interactive in both plain and rich variants. The Trigger needs a complete accessible name independently; Tooltip provides only a supplemental description.

KeyDescription
TabMoving focus to a focus-visible trigger can open the tooltip.
Shift+TabMoving focus away closes the tooltip.
EscapeCloses the topmost open tooltip immediately.

On touch devices, a stationary 700 ms press opens Tooltip immediately without adding the hover openDelay. Touch-generated compatibility hover/focus events do not open Tooltip after a quick tap. Ordinary taps and scrolling are not suppressed; native text selection and the context callout are suppressed only while the Trigger is tracking the competing stationary long-press gesture. An opened Tooltip remains visible while the initiating finger is down. After release, plain dismisses after 1500 ms and rich after 3000 ms; an outside touch or scroll dismisses either immediately. Moving more than 10 CSS pixels, scrolling, adding a second touch, receiving touchcancel, disabling the Trigger, or unmounting it cancels the touch session.

Changelog

Unreleased

0.24.0

  • Added public Agent Knowledge for component selection, required composition, recurring mistakes, and validation.
  • Preserved centered Content alignment near viewport edges by shifting it into view before considering alternate placements.

0.6.9

  • Preserved resolved Trigger or provider direction on portalled Content while retaining explicit Content dir precedence.

0.6.8

  • Exposed measured available dimensions through headless floating properties for constrained styled surfaces.

0.6.3

  • Prioritized every usable alignment on the requested side, followed by the opposite side, before allowing perpendicular-axis collision fallbacks.

0.6.2

  • Added perpendicular-side collision fallbacks after the preferred and opposite sides so constrained tooltips can resolve onto the axis with available room.

0.6.1

  • Added immediate outside-touch and scroll dismissal after a long-press Tooltip has opened and the initiating finger is released.

0.3.5

  • Corrected touch long press to open once at the 700 ms threshold without also paying the hover delay.
  • Added complete touch-session cancellation for early release, movement beyond 10 CSS pixels, scrolling, a second touch, touchcancel, disabled changes, and Trigger unmount.
  • Ignored touch-generated compatibility hover/focus events after release so a quick tap cannot enter the desktop opening path, and suppressed native text selection/callout only while a long press is actively being tracked.
  • Moved touch auto-dismissal to release time, retaining 1500 ms for plain and adding a finite 3000 ms rich dismissal.
  • Clarified that plain and rich Content are both non-interactive and that actionable floating content belongs in Popover.

0.3.1

  • Fixed exit-presence cleanup so closed Tooltip Content unmounts after its CSS motion window even when no end event is emitted.

0.2.0

  • Fixed Tooltip render trigger positioning by updating Floating UI after the trigger ref commits.
  • Added data-variant="plain|rich" to Tooltip content and documented the variant Root prop.
  • Added shared dismissable layer Escape handling so Tooltip participates in topmost-layer dismissal with other overlays.

0.1.0

  • Initial Atom release.