Tooltip
Overviewimport { Tooltip, TooltipAlign, TooltipPlacement, TooltipSide } from '@nili/ui';This is the library’s own overview page, rendered live. Switch the language or the theme in the header and it follows.
Sizes
Three bubble sizes. The trigger must be focusable — a tooltip on a plain span is invisible to keyboard users, so wrap text in a button.
import { Tooltip, Button } from "@nili/ui"
export function Example() {
return (
<>
<Tooltip size="2xs" content={t('copyLink')}>
<Button appearance="stroke">2X-Small</Button>
</Tooltip>
<Tooltip size="xs" content={t('copyLink')}>
<Button appearance="stroke">X-Small</Button>
</Tooltip>
<Tooltip size="lg" content="Copy link" description="Anyone with the link can view.">
<Button appearance="stroke">Large</Button>
</Tooltip>
</>
)
}Sides and alignment
side is logical: start and end follow reading direction. Top and bottom also take an align; start and end do not, and the type makes that unrepresentable rather than accepting a prop that does nothing.
import { Tooltip, Button } from "@nili/ui"
export function Example() {
return (
<>
<Tooltip side="top" content="Top" />
<Tooltip side="bottom" align="start" content="Bottom start" />
<Tooltip side="start" content="Start" />
<Tooltip side="end" content="End" />
</>
)
}Light bubble, arrow and leading icon
A tooltip is inverted against its surface, so darkMode is a per-tooltip choice rather than a theme decision. It is on by default — the dark bubble on a light page.
import { Tooltip, CompactButton, Icon } from "@nili/ui"
import { RiInformationLine } from "@remixicon/react"
export function Example() {
return (
<>
<Tooltip content={t('style.dark')}>
<CompactButton aria-label="Info"><Icon icon={RiInformationLine} /></CompactButton>
</Tooltip>
<Tooltip darkMode={false} content={t('style.light')}>
<CompactButton aria-label="Info"><Icon icon={RiInformationLine} /></CompactButton>
</Tooltip>
<Tooltip arrow={false} content={t('style.noTail')}>
<CompactButton aria-label="Info"><Icon icon={RiInformationLine} /></CompactButton>
</Tooltip>
<Tooltip
content="With a leading icon"
startIcon={<Icon icon={RiInformationLine} />}
>
<CompactButton aria-label="Info"><Icon icon={RiInformationLine} /></CompactButton>
</Tooltip>
</>
)
}Delay and dismissal
delay holds the bubble back on hover; focus always opens it immediately. The large tooltip can carry a close button — keep that for content people need a moment with.
import { Tooltip, Button } from "@nili/ui"
export function Example() {
return (
<>
<Tooltip delay={600} content={t('delay.waited')}>
<Button appearance="stroke">Slow</Button>
</Tooltip>
<Tooltip
size="lg"
dismissible
dismissLabel="Close"
content="Keyboard shortcuts"
description="Press ⌘K to open the command palette from anywhere."
>
<Button appearance="stroke">{t('delay.dismissible')}</Button>
</Tooltip>
<Tooltip disabled content={t('delay.offBody')}>
<Button appearance="stroke" disabled>Disabled</Button>
</Tooltip>
</>
)
}Tooltip
Variant axes — from the component’s cva definition
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | 2xs | xs | lg | xs | |
| darkMode | 'true' | 'false' | true | |
| side | top | bottom | start | end | top | |
| align | start | center | end | center |
TooltipArrow
internalVariant axes — from the component’s cva definition
| Prop | Type | Default | Notes |
|---|---|---|---|
| darkMode | 'true' | 'false' | true | |
| side | top | bottom | start | end | top |
TooltipArrowSurface
internalVariant axes — from the component’s cva definition
| Prop | Type | Default | Notes |
|---|---|---|---|
| darkMode | 'true' | 'false' | true |
TooltipBase
internalextends Omit< ComponentPropsWithoutRef<'div'>, 'content' >
Other props
| Prop | Type | Default | Notes |
|---|---|---|---|
| children* | ReactNode | — | The trigger. A single element, which receives the hover, focus and `aria-describedby` wiring — so it must be able to take focus. Wrap plain text in a `<button>` or a focusable element; a tooltip on a `<span>` is invisible to keyboard users. |
| aria-label | string | — | |
| content* | ReactNode | — | Tooltip text. Keep it to a phrase and keep it non-essential: a tooltip is unreachable on touch and can be missed entirely, so anything the user *must* read belongs in the page. |
| description | ReactNode | — | Second line under the text. Figma's "Edit Description". |
| startIcon | ReactNode | — | Leading slot inside the bubble. Figma's "Left Icon". |
| size | lg | xs | 2xs | — | |
| arrow | boolean | — | Figma's "Tail". |
| darkMode | boolean | — | Figma's "Dark Mode". A tooltip is inverted against its surface, so this is a per-tooltip choice rather than something the theme decides. On by default, which is the dark bubble on a light page. |
| open | boolean | — | Controlled visibility. |
| defaultOpen | boolean | — | |
| onOpenChange | (open: boolean) => void | — | |
| delay | number | — | Milliseconds before it appears on hover. Focus opens it immediately. |
| disabled | boolean | — | Turns the tooltip off without unmounting the trigger. |
| dismissible | boolean | — | Figma's large tooltip carries a close (×) button. |
| onDismiss | () => void | — | |
| dismissLabel | string | — | Accessible name for the close button. |
| contentClassName | string | — | Class for the bubble. Use `className` for the wrapper. |
TooltipSurface
internalVariant axes — from the component’s cva definition
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | 2xs | xs | lg | xs | |
| darkMode | 'true' | 'false' | true |