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';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
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
| Prop | Type | |
|---|---|---|
| className | string | Added to the panel’s own classes, so yours wins |
| style | CSSProperties | Merged 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.
| Prop | Type | Default | |
|---|---|---|---|
| children | ReactNode | — | The thing being described |
| content | ReactNode | — | The label. Nothing opens with nothing to say |
| open | boolean | — | Controlled |
| defaultOpen | boolean | false | |
| onOpenChange | (open: boolean) => void | — | |
| placement | Side | 'top' | Flips only if the opposite side is better |
| align | Align | 'center' | |
| gap | number | 6 | Between the trigger and the tip |
| offset | number | 8 | Smallest distance kept from the viewport’s edges |
| maxWidth | number | 240 | Past this the label wraps |
| delay | number | 600 | Milliseconds a pointer has to rest. Focus never waits |
| skip | number | 300 | How long the group stays warm after the last one closes |
| zIndex | number | 70 | Above the dialog’s 60 |
| container | HTMLElement | null | document.body | Where it portals to |
| disabled | boolean | false | |
| theme | 'dark' | 'light' | 'dark' |