Overlay

Tooltip

A label that appears next to what it names. It is the densest glass in the library, because it is small, it lands on whatever happens to be under it, and there is no room for a wash to settle.

Usage

import { Tooltip } from '@carabine/ui/tooltip';
<Tooltip content="Copy link">
  <Button variant="ghost" aria-label="Copy link" icon={<Copy />} />
</Tooltip>

The tip is not the name. An icon-only control keeps its own aria-label — a screen reader never sees the tooltip, and a button whose only name is a hover state is a button with no name.

The group

The first tooltip waits delay milliseconds of the pointer resting. Once one has opened, the group stays warm for skip milliseconds, and every tip in it opens immediately — which is what makes a row of icon buttons feel like a toolbar rather than like four separate waits.

Focus never waits. A keyboard user has already committed by arriving, and a delay there is just latency.

Examples

Delay

<Tooltip content="Waits 600ms" delay={600}>…</Tooltip>
<Tooltip content="Answers at once" delay={0}>…</Tooltip>

Customization

<Tooltip
  content="Copy link"
  placement="right"
  align="start"
  gap={6}         // between the trigger and the tip
  offset={8}      // smallest distance kept from the window's edges
  maxWidth={240}  // past this the label wraps
  delay={0}
  skip={300}      // how long the group stays warm after the last one closes
  zIndex={70}     // above the dialog's 60: a tip inside a dialog still shows
  theme="light"
/>

Styling reference

Denser than everything else — /95 with a blur-sm. A popover is a panel you read against and it can afford a wash; a tooltip is twenty pixels tall and lands on whatever happens to be underneath, so there is nowhere for a wash to settle.

125–200ms in, faster out, and it grows from the edge nearest its trigger like everything else anchored. prefers-reduced-motion keeps a short opacity tween rather than nothing — a tip that blinks into existence reads as a rendering fault.

API reference

PropType
classNamestringAdded to the panel’s own classes, so yours wins
styleCSSPropertiesMerged after the component’s own inline styles, so yours wins

Both land on the panel. The trigger is already your element — you style it where you write it — and the panel is the part that portals away from your markup and is otherwise out of reach. State is legible from CSS as well — data-state, data-disabled, data-side — so a rule can answer it without knowing a single class name of ours. See Theming.

PropTypeDefault
childrenReactNodeThe thing being described
contentReactNodeThe label. Nothing opens with nothing to say
openbooleanControlled
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void
placementSide'top'Flips only if the opposite side is better
alignAlign'center'
gapnumber6Between the trigger and the tip
offsetnumber8Smallest distance kept from the viewport’s edges
maxWidthnumber240Past this the label wraps
delaynumber600Milliseconds a pointer has to rest. Focus never waits
skipnumber300How long the group stays warm after the last one closes
zIndexnumber70Above the dialog’s 60
containerHTMLElement | nulldocument.bodyWhere it portals to
disabledbooleanfalse
theme'dark' | 'light''dark'