Tooltip

A short label shown on hover (after a delay) or keyboard focus. Touch is ignored. No ARIA is added; give the trigger its own accessible name.

WAI-ARIA pattern

Installation

npx nuecrea-ui add tooltip

Usage

import { Button } from '@/components/ui/button';
import * as Tooltip from '@/components/ui/tooltip';

function Tip({
  label,
  tip,
  side,
  arrowPadding,
  disabled,
}: {
  label: string;
  tip: string;
  side?: 'top' | 'bottom' | 'left' | 'right';
  arrowPadding?: number;
  disabled?: boolean;
}) {
  return (
    <Tooltip.Root disabled={disabled}>
      <Tooltip.Trigger render={<Button variant="outline" size="sm" />}>
        {label}
      </Tooltip.Trigger>
      <Tooltip.Portal>
        <Tooltip.Positioner side={side} arrowPadding={arrowPadding}>
          <Tooltip.Popup>
            <Tooltip.Arrow />
            {tip}
          </Tooltip.Popup>
        </Tooltip.Positioner>
      </Tooltip.Portal>
    </Tooltip.Root>
  );
}

export default function TooltipStory() {
  return (
    <Tooltip.Provider>
      <main className="relative h-[520px]">
        {/* At the very top: side="top" has no room and flips to bottom. */}
        <div className="absolute top-0 left-40">
          <Tip label="Save" tip="Save (⌘S)" />
        </div>
        {/* A toolbar: once one tooltip has shown, the next opens without delay. */}
        <div className="absolute top-40 left-40 flex gap-2">
          <Tip label="Bold" tip="Bold (⌘B)" />
          <Tip label="Italic" tip="Italic (⌘I)" />
          <Tip label="Underline" tip="Underline (⌘U)" side="bottom" />
          <Tip label="Locked" tip="Not shown" disabled />
        </div>
        {/* Against the left edge with a large arrowPadding: the arrow is uncentered. */}
        <div className="absolute top-72 left-0">
          <Tip label="Help" tip="Keyboard shortcuts and docs" arrowPadding={70} />
        </div>
        {/* Room to scroll a trigger out of view (data-anchor-hidden). */}
        <div aria-hidden="true" className="absolute top-full h-[600px] w-px" />
      </main>
    </Tooltip.Provider>
  );
}

API reference

Tooltip.Provider

Groups tooltips: after one shows, the next opens instantly within `timeout`.

PropTypeDefaultDescription
delaynumber—Overrides each trigger's open delay.
closeDelaynumber—Overrides each trigger's close delay.
timeoutnumber400Window after a tooltip closes in which the next opens instantly.

Tooltip.Root

Owns one tooltip's open state.

PropTypeDefaultDescription
openboolean—Controlled open state.
defaultOpenbooleanfalseInitial open state.
disabledbooleanfalseNever opens.
disableHoverablePopupbooleanfalseClose as soon as the pointer leaves the trigger.
EventPayloadDescription
onOpenChangebooleanAngular: the `open` model's change event.
onOpenChangeCompletebooleanAfter the animation finishes.

Tooltip.Trigger

Shows the tooltip on hover and keyboard focus. Renders <button>.

PropTypeDefaultDescription
delaynumber600Hover delay before opening (ms).
closeDelaynumber0Delay before closing (ms).
closeOnClickbooleantrueClose when the trigger is pressed.
disabledbooleanfalseDisables the tooltip (not the trigger).
Data attributePresent when
data-popup-openopen
data-trigger-disabledthe tooltip is disabled

Tooltip.Portal

Moves the tooltip to the end of <body>. Renders <div>.

PropTypeDefaultDescription
keepMountedbooleanfalseKeep in the DOM while closed.

Tooltip.Positioner

Positions the tooltip; same options as Popover, default side top. Renders <div> with role presentation.

PropTypeDefaultDescription
side'top' | 'bottom' | 'left' | 'right'"top"Preferred side; flips on collision.
align'start' | 'center' | 'end'"center"Alignment.
sideOffsetnumber0Gap (styled default 8).
alignOffsetnumber0Alignment shift.
arrowPaddingnumber5Arrow distance from popup edges.
collisionPaddingnumber5Distance from viewport edges.
Data attributePresent when
data-openopen
data-closedclosed / animating out
data-anchor-hiddentrigger scrolled out of view
data-alignalways: resolved alignment
data-sidealways: resolved side
data-instant'focus' (keyboard focus), 'delay' (group handover), 'dismiss' (Escape): skip animation

Tooltip.Arrow

Points at the trigger. Renders <div>.

Data attributePresent when
data-openopen
data-closedclosed / animating out
data-uncenteredclamped by arrowPadding
data-alignalways: resolved alignment
data-instant'focus' (keyboard focus), 'delay' (group handover), 'dismiss' (Escape): skip animation
data-sidealways: resolved side

Keyboard

KeyAction
TabFocusing the trigger with the keyboard shows the tooltip at once.
EscapeHides it.