Tooltip
A short label shown on hover and keyboard focus, on an inverse surface. The trigger can be any focusable element, a server Button included.
Import
import { Tooltip } from '@nizivo/react/tooltip';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/kbd.css
import '@nizivo/react/css/tooltip.css';Static or interactive
Client. It always ships React Aria's tooltip and overlay code (+19,625 B gzip under Turbopack for one Tooltip on the Next.js fixture), once per page however many tooltips it has. Information a user needs belongs in visible text; for an icon-only action, IconButton pairs the tooltip for you. How to choose.
The trigger
Compose a Button into the trigger; it is still the recommended look:
<Tooltip.Trigger render={<Button variant="tertiary">Export</Button>} />For an icon-only action, use an IconButton: it pairs itself with a
Tooltip that shows its label, so it needs no Tooltip.Root.
A bare <Tooltip.Trigger> is a plain, unstyled <button>, but it has a minimum size of its own: at
least 24 by 24 CSS pixels around a short text or a single glyph, the minimum target of WCAG 2.2 success
criterion 2.5.8 (Target Size, Minimum), so two side by side still meet it. It also carries
nz-hit-target, so under a coarse pointer it gets the same 44 px hit area as every standalone
pressable. A trigger you render yourself through render keeps its own size and gets neither.
A trigger inside running text, such as a word in a sentence that shows a tooltip, is the exception:
render your own element through render there. The 24 px minimum would make its line taller than
the others, and under a coarse pointer the 44 px hit area would reach into the lines above and below
and take taps meant for a link there. WCAG 2.2 2.5.8 exempts a target inside a sentence, so the
inline element needs neither.
Examples
Basic
Compose a Button (or an IconButton) into Tooltip.Trigger: it brings the control look and the 40 px control height. The bare part is an unstyled <button> with only the WCAG 2.2 2.5.8 minimum of 24 x 24 px (and the 44 px hit area under a coarse pointer; M27).
import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Tooltip } from '@nizivo/react/tooltip';
export function TooltipExample(): JSX.Element {
return (
<Tooltip.Root>
<Tooltip.Trigger render={<Button variant="tertiary">Export</Button>} />
<Tooltip.Popup side="end">Download the report as CSV</Tooltip.Popup>
</Tooltip.Root>
);
}
Shortcut
content renders the Popup for you, and shortcut draws the keys with Kbd: ⌘E on Apple devices once ThemeScript has run, Ctrl+E elsewhere.
import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Tooltip } from '@nizivo/react/tooltip';
export function TooltipShortcutExample(): JSX.Element {
return (
<Tooltip.Root content="Export the report" shortcut="mod+e">
<Tooltip.Trigger render={<Button variant="secondary">Export</Button>} />
</Tooltip.Root>
);
}
Open
Open on the server; the trigger is a composed Button, as everywhere.
Source only
defaultOpen), which would move focus on this page, so it is shown as source only.import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Tooltip } from '@nizivo/react/tooltip';
export function TooltipOpenExample(): JSX.Element {
return (
<Tooltip.Root defaultOpen delay={0}>
<Tooltip.Trigger render={<Button variant="secondary">Refresh</Button>} />
<Tooltip.Popup>Refresh data</Tooltip.Popup>
</Tooltip.Root>
);
}
Parts
Use the parts through the namespace (Tooltip.Root) or by their flat names (TooltipRoot); both work from Server Components.
Tooltip.Root
Client component Renders no element of its own. Props: TooltipRootProps. Source: src/tooltip/tooltip.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | – | |
content | ReactNode | – | Convenience for the common case: renders a |
defaultOpen | boolean | – | |
delay | number | 400 | Hover delay before opening, in ms (Figma: 400). Keyboard focus opens immediately. |
disabled | boolean | – | |
onOpenChange | (open: boolean, details: ChangeDetails<OpenReason>) => void | – |
|
open | boolean | – | |
shortcut | string | – | A shortcut for |
Tooltip.Trigger
Without render, its own <button>: nz-hit-target gives it the 44 px coarse-pointer hit area of every standalone pressable, and tooltip.css gives that bare button a 24 x 24 px minimum (WCAG 2.2 2.5.8; M27). A composed Button or IconButton keeps its own size and classes.
Client component Renders data-slot="tooltip-trigger" with the class nz-tooltip-trigger. Props: TooltipTriggerProps. Source: src/tooltip/tooltip.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | |
render | RenderProp<TooltipTriggerState> | – | Any focusable element, for example |
- Also takes every attribute of
<button>exceptchildren. (Omit<ComponentPropsWithRef<"button">, "children">)
render takes an element, or, in client code, a function of the props and this state (TooltipTriggerState):
| Name | Type | Default | Description |
|---|---|---|---|
openrequired | boolean | – |
Tooltip.Popup
Client component Renders data-slot="tooltip-popup" with the class nz-tooltip-popup. Props: TooltipPopupProps. Source: src/tooltip/tooltip.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
align | Align"start""center""end" | 'center' | |
childrenrequired | ReactNode | – | |
container | Element | null | – | Portal container. Defaults to the parent overlay, else |
shortcut | string | – | A keyboard shortcut drawn after the text with |
side | TooltipSide"top""bottom""start""end" | 'top' | |
sideOffset | number | 8 | Distance from the trigger in px. |
- Also takes every attribute of
<div>exceptchildren,role. (Omit<ComponentPropsWithRef<"div">, "children" | "role">)
Other exports
TooltipLabelstypeimport type { TooltipLabels } from '@nizivo/react/tooltip'Strings the engine renders (§9.3 rule 7): React Aria's Tooltip renders none.
type TooltipLabels = Record<string, never>TooltipPopupPropstypeimport type { TooltipPopupProps } from '@nizivo/react/tooltip'The props of
Tooltip.Popup, listed above.TooltipPopupStatetypeimport type { TooltipPopupState } from '@nizivo/react/tooltip'Name Type Default Description enteringrequiredboolean– exitingrequiredboolean– siderequiredSide"top""right""bottom""left"– TooltipRootPropstypeimport type { TooltipRootProps } from '@nizivo/react/tooltip'The props of
Tooltip.Root, listed above.TooltipSidetypeimport type { TooltipSide } from '@nizivo/react/tooltip'Logical side; the resolved physical side is
data-sideon the popup.type TooltipSide = "top" | "bottom" | "start" | "end"TooltipTriggerPropstypeimport type { TooltipTriggerProps } from '@nizivo/react/tooltip'The props of
Tooltip.Trigger, listed above.TooltipTriggerStatetypeimport type { TooltipTriggerState } from '@nizivo/react/tooltip'The render state of
Tooltip.Trigger, listed above.
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
tooltip-popup.nz-tooltip-popup | Tooltip.Popuproot (no render); forced colours: border |
|
tooltip-trigger.nz-tooltip-trigger | Tooltip.Triggerroot (takes render); interactive, focus: self |
|
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-tooltip-bg | --nz-bg-surface-raised |
|
--nz-tooltip-fg | --nz-text-primary |
|
--nz-tooltip-radius | --nz-radius-sm |
|
--nz-tooltip-shadow | --nz-elevation-3 |
|
--nz-tooltip-shortcut-fg | --nz-text-tertiary |
|
Layers: nz.components. Classes: .nz-hit-target, .nz-tooltip-popup, .nz-tooltip-shortcut, .nz-tooltip-trigger. Runtime variables: --nz-transform-origin.
Tokens this stylesheet reads (15)
--nz-bg-surface-raised, --nz-distance-lift, --nz-duration-fade, --nz-duration-fast, --nz-easing-enter, --nz-easing-exit, --nz-elevation-3, --nz-font-label-m, --nz-radius-sm, --nz-space-2, --nz-space-3, --nz-text-primary, --nz-text-tertiary, --nz-tracking-label-m, --nz-z-overlay
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
tooltip-trigger: draws the focus outline itself on keyboard focus.
In forced-colours mode these states keep a visible system colour: tooltip-popup (border).
Behaviour comes from the react-aria engine through Nizivo's adapter: focus management, ARIA wiring and keyboard handling are the engine's.
Pattern: WAI-ARIA Authoring Practices.
Keyboard
| Keys | Action |
|---|---|
| Tab | Focusing the trigger with the keyboard shows the tooltip at once; the trigger points at it with aria-describedby. |
| Escape | Hides the tooltip; focus stays on the trigger. |
Source: react-aria-components 1.22.1 TooltipTrigger (react-aria 3.53.1 useTooltipTrigger) through _engines/react-aria/tooltip.client.tsx. Showing on keyboard focus and hiding on Escape are checked by the Next fixture and by apps/docs/scripts/smoke.ts.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: none
- Ghost masters: Figma node
746:262
Toast
A short message after an action, bottom-centre, with one optional action. toast() shows it from anywhere in your client code; one <Toaster /> in the root layout renders the stack and speaks each message to screen readers.
TopBar
The bar above the page on screens 640 px and wider, with the page's name or a Breadcrumb at the start, the search, then notifications and the account.