Skip to main content
Markdown

Tooltip

Tooltip displays a floating label on hover or focus to provide brief contextual information. It wraps the Spar headless Tooltip primitive and adds Takeoff visual vocabulary — variant, header, description, and arrow slots.

Usage

import { Tooltip } from '@takeoff-ui/react-spar';
<Tooltip>
<Tooltip.Trigger />
<Tooltip.Content>
<Tooltip.Header />
<Tooltip.Description />
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip>

To share delay configuration across a group of tooltips, wrap them in Tooltip.Provider:

<Tooltip.Provider delayDuration={300} skipDelayDuration={150}>
{/* multiple <Tooltip /> instances */}
</Tooltip.Provider>

Playground


function PlaygroundDemo() {
  return (
    <Tooltip>
      <Tooltip.Trigger as={Button}>Hover me</Tooltip.Trigger>
      <Tooltip.Content>
        <Tooltip.Description>Simple tooltip</Tooltip.Description>
      </Tooltip.Content>
    </Tooltip>
  );
}

render(<PlaygroundDemo />);

Variants


function VariantsDemo() {
  return (
    <div className="flex flex-wrap justify-center gap-3">
      <Tooltip>
        <Tooltip.Trigger as={Button}>White</Tooltip.Trigger>
        <Tooltip.Content variant="white">
          <Tooltip.Description>White variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Dark</Tooltip.Trigger>
        <Tooltip.Content variant="dark">
          <Tooltip.Description>Dark variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Info</Tooltip.Trigger>
        <Tooltip.Content variant="info">
          <Tooltip.Description>Info variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Success</Tooltip.Trigger>
        <Tooltip.Content variant="success">
          <Tooltip.Description>Success variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Warning</Tooltip.Trigger>
        <Tooltip.Content variant="warning">
          <Tooltip.Description>Warning variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Danger</Tooltip.Trigger>
        <Tooltip.Content variant="danger">
          <Tooltip.Description>Danger variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Neutral</Tooltip.Trigger>
        <Tooltip.Content variant="neutral">
          <Tooltip.Description>Neutral variant</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
    </div>
  );
}

render(<VariantsDemo />);

Placement


function PlacementDemo() {
  return (
    <div className="flex flex-wrap justify-center gap-3">
      <Tooltip>
        <Tooltip.Trigger as={Button}>Top</Tooltip.Trigger>
        <Tooltip.Content side="top">
          <Tooltip.Description>Positioned top</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Bottom</Tooltip.Trigger>
        <Tooltip.Content side="bottom">
          <Tooltip.Description>Positioned bottom</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Left</Tooltip.Trigger>
        <Tooltip.Content side="left">
          <Tooltip.Description>Positioned left</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Tooltip>
        <Tooltip.Trigger as={Button}>Right</Tooltip.Trigger>
        <Tooltip.Content side="right">
          <Tooltip.Description>Positioned right</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
    </div>
  );
}

render(<PlacementDemo />);

Header & Description


function HeaderDemo() {
  return (
    <Tooltip>
      <Tooltip.Trigger as={Button}>With header</Tooltip.Trigger>
      <Tooltip.Content variant="info">
        <Tooltip.Header>Important</Tooltip.Header>
        <Tooltip.Description>This tooltip has a header and description.</Tooltip.Description>
      </Tooltip.Content>
    </Tooltip>
  );
}

render(<HeaderDemo />);

Arrow


function ArrowDemo() {
  return (
    <Tooltip>
      <Tooltip.Trigger as={Button}>With arrow</Tooltip.Trigger>
      <Tooltip.Content>
        <Tooltip.Description>Tooltip with arrow</Tooltip.Description>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip>
  );
}

render(<ArrowDemo />);

Provider

Wrap a group of tooltips in Tooltip.Provider to share delayDuration and a skipDelayDuration window — once one tooltip in the group has opened, moving to a sibling within the skip window opens it instantly. Recommended for toolbar-style clusters (WCAG 1.4.13).


function ProviderDemo() {
  return (
    <Tooltip.Provider delayDuration={300} skipDelayDuration={150}>
      <div className="flex flex-wrap justify-center gap-3">
        <Tooltip>
          <Tooltip.Trigger as={Button}>First</Tooltip.Trigger>
          <Tooltip.Content>
            <Tooltip.Description>Hover, then move to the next one</Tooltip.Description>
          </Tooltip.Content>
        </Tooltip>
        <Tooltip>
          <Tooltip.Trigger as={Button}>Second</Tooltip.Trigger>
          <Tooltip.Content>
            <Tooltip.Description>Opens instantly within skip window</Tooltip.Description>
          </Tooltip.Content>
        </Tooltip>
        <Tooltip>
          <Tooltip.Trigger as={Button}>Third</Tooltip.Trigger>
          <Tooltip.Content>
            <Tooltip.Description>Same skip-delay behavior</Tooltip.Description>
          </Tooltip.Content>
        </Tooltip>
      </div>
    </Tooltip.Provider>
  );
}

render(<ProviderDemo />);

Controlled


function ControlledDemo() {
  const [open, setOpen] = React.useState(false);

  return (
    <div className="flex flex-wrap justify-center gap-3">
      <Tooltip open={open} onOpenChange={setOpen}>
        <Tooltip.Trigger as={Button}>Controlled</Tooltip.Trigger>
        <Tooltip.Content>
          <Tooltip.Description>Controlled tooltip</Tooltip.Description>
        </Tooltip.Content>
      </Tooltip>
      <Button onClick={() => setOpen(!open)}>{open ? 'Hide' : 'Show'}</Button>
    </div>
  );
}

render(<ControlledDemo />);

Accessibility & Keyboard

  • Trigger uses aria-describedby pointing to the content element.
  • Content has role="tooltip".
  • Tooltip respects WCAG 1.4.13 — hoverable content remains visible while the pointer is over it.
  • Escape key dismisses the tooltip and returns focus to the trigger.
KeyBehavior
EscapeDismiss the tooltip.
TabFocus trigger shows tooltip; blur hides it.

API Reference

Tooltip

See Spar Tooltip docs for primitive behavior.

Props

NameTypeDefaultDescription
childrenReact.React.ReactNode-Tooltip trigger and content components
idstring-Custom base ID for ARIA relationships. If not provided, one will be generated automatically. Sub-element IDs are derived as ${id}-trigger and ${id}-content.
disabledbooleanfalseWhether tooltip is disabled
openboolean-Controlled state for tooltip visibility
defaultOpenbooleanfalseDefault open state for uncontrolled tooltip
delaynumber-Override provider delay for this tooltip
hideDelaynumber0Override provider hide delay for this tooltip

Events

NameTypeDefaultDescription
onOpenChange(open: boolean) => void-Callback when tooltip open state changes

Tooltip.Content

Props

NameTypeDefaultDescription
childrenReact.React.ReactNode-
variantTooltipVariant'white'Color variant.
classNamesPartial<Record<"root", string>>-Per-slot extra classes.
slotPropsPartial<Record<"root", React.HTMLAttributes<HTMLElement>>>-Per-slot HTML-attribute overrides.
containerHTMLElement | nulldocument.bodyPortal container element. Content is portaled to document.body by default.
sideSide'top'Preferred placement relative to trigger
alignAlign'center'Alignment relative to trigger
classNamestring-

Events

NameTypeDefaultDescription
onOpenAutoFocus(event: Event) => void-Called when auto-focusing on open
onCloseAutoFocus(event: Event) => void-Called when auto-focusing on close
onEscapeKeyDown(event: KeyboardEvent) => void-Escape key handler
onPointerDownOutside(event: PointerEvent) => void-Outside pointer down handler

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-variantAlwaysReflects the resolved variant for theme recipe scoping.

Tooltip.Header

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for the header.

Tooltip.Description

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for the description.

Tooltip.Trigger

Tooltip.Arrow

Type Definitions

NameDefinition
TooltipVariant'white' | 'dark' | 'info' | 'success' | 'warning' | 'danger' | 'neutral'
Side'top' | 'right' | 'bottom' | 'left'
Align'start' | 'center' | 'end'
TooltipTriggerRenderProps{ isOpen: boolean; disabled: boolean; show: () => void; hide: () => void }