Popover
A small dialog anchored to its trigger, for status, quick settings and filters. It has a title, a description, any controls, and a close button; Escape or a press outside closes it, and focus returns to the trigger.
Import
import { Popover } from '@nizivo/react/popover';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/icon-button.css
import '@nizivo/react/css/popover.css';Static or interactive
Client. It always ships React Aria's dialog, popover and positioning code: @nizivo/react/popover
measures 35.0 KiB gzip with React Aria inside (size-limit, all locales; packages/react/SIZES.md).
Most of it is shared with Menu and Select, so a page that already has one of them pays far less. How
to choose.
Popover, Menu, Tooltip or Dialog
- A Menu lists actions or links; a Select chooses a value. Use them when the popup is a list.
- A Tooltip names or explains its trigger in a few words and holds nothing to press.
- A Popover holds content and controls next to the control that opened it: a store's status with a "Pause" action, quick filters, a notifications list.
- A Dialog asks for the whole page's attention. Use it for a task the user must finish or dismiss before going on.
Placement
Popover.Popup takes side (top, bottom, start, end; default bottom), align (start,
center, end; default center) and sideOffset (default 8 px). start and end follow the
writing direction. When there is no room, it flips to the other side; data-side on the popup is the
side it ended on, as a physical top, right, bottom or left, and data-align the alignment.
It is as wide as its content, up to 22.5rem (360 px) and the viewport less 32 px. Set
--nz-popover-width for a fixed width. Its content scrolls when it is taller than the room below
(or above) the trigger.
There is no arrow: Figma's popups (the Dropdown Menu and the Date Range Picker) have none.
Naming it
A Popover.Title names the popover. Without one, give Popover.Popup an aria-label:
<Popover.Popup aria-label="Filters">…</Popover.Popup>The close button
<Popover.Close /> with no render and no children is a small IconButton (tertiary, sm)
showing the close glyph, named "Close" (labels={{ close: '…' }} or aria-label renames it). As a
direct child of Popover.Popup it sits in the top end corner, and the title leaves room for it. With
children it is a plain button; with render, your element, for example
render={<Button size="sm">Done</Button>}. Its markup is IconButton's, so a page that imports
per-component stylesheets needs css/icon-button.css too.
Inside other overlays
A Popover opened inside a Dialog or a Drawer portals into that overlay and paints above it, and a Select, Menu, Tooltip or Dialog opened inside a Popover paints above the Popover. Escape and a press outside close only the topmost one; the page stays scroll-locked until the last one closes, and focus walks back trigger by trigger.
While it is open the rest of the page is inert and does not scroll (React Aria's popovers are modal), as with Menu and Select.
Examples
Basic
A notifications-style popover from a composed Button: a title, a description, an action, and the default close in the corner. Escape, an outside press or the close button close it, and focus returns to the trigger. Opened, it focuses its first control (the corner close is skipped).
import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Popover } from '@nizivo/react/popover';
export function PopoverExample(): JSX.Element {
return (
<Popover.Root>
<Popover.Trigger render={<Button variant="secondary">Store status</Button>} />
<Popover.Popup>
<Popover.Title level={3}>Store is live</Popover.Title>
<Popover.Close />
<Popover.Description>
Customers can see 42 products. Pause the store to hide them while you restock.
</Popover.Description>
<Button size="sm">Pause store</Button>
</Popover.Popup>
</Popover.Root>
);
}
Open
Open on the server: closed overlays and server-rendered opens both emit no popup HTML (§12.2 SSR). The popup opens after hydration, placed at the trigger's end.
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 { Popover } from '@nizivo/react/popover';
export function PopoverOpenExample(): JSX.Element {
return (
<Popover.Root defaultOpen>
<Popover.Trigger render={<Button variant="secondary">Filters</Button>} />
<Popover.Popup side="end" align="start" aria-label="Filters">
<Popover.Description>No filters applied.</Popover.Description>
<Popover.Close render={<Button size="sm">Done</Button>} />
</Popover.Popup>
</Popover.Root>
);
}
Parts
Use the parts through the namespace (Popover.Root) or by their flat names (PopoverRoot); both work from Server Components.
Popover.Root
Client component Renders no element of its own. Props: PopoverRootProps. Source: src/popover/popover.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | – | |
defaultOpen | boolean | – | |
onOpenChange | (open: boolean, details: ChangeDetails<OpenReason>) => void | – |
|
open | boolean | – |
Popover.Trigger
Without render, its own <button>: nz-hit-target gives it the 44 px coarse-pointer hit area of every standalone pressable, and popover.css gives that bare button a 24 x 24 px minimum (WCAG 2.2 2.5.8, as Dialog's and Tooltip's, M27). A composed Button or IconButton keeps its own size.
Client component Renders data-slot="popover-trigger" with the class nz-popover-trigger. Props: PopoverTriggerProps. Source: src/popover/popover.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | |
render | RenderProp<PopoverTriggerState> | – | Must render a |
- 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 (PopoverTriggerState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
focusVisiblerequired | boolean | – | |
openrequired | boolean | – | |
pressedrequired | boolean | – |
Popover.Popup
Client component Renders data-slot="popover-popup" with the class nz-popover-popup. Props: PopoverPopupProps. Source: src/popover/popover.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
align | Align"start""center""end" | 'center' | |
aria-describedby | string | – | |
aria-label | string | – | Names the popup when it has no |
aria-labelledby | string | – | |
childrenrequired | ReactNode | – | |
className | string | – | |
container | Element | null | – | Portal container. Defaults to the parent overlay, else |
side | PopoverSide"top""bottom""start""end" | 'bottom' | |
sideOffset | number | 8 | Distance from the trigger in px. |
style | CSSProperties | – |
Popover.Title
Client component Renders data-slot="popover-title" with the class nz-popover-title. Props: PopoverTitleProps. Source: src/popover/popover.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
level | PopoverTitleLevel23456 | 2 | |
render | RenderProp<Record<string, never>> | – |
- Also takes every attribute of
<h2>exceptid. (Omit<ComponentPropsWithRef<"h2">, "id">)
render takes an element, or, in client code, a function of the props and no state.
Popover.Description
Client component Renders data-slot="popover-description" with the class nz-popover-description. Props: PopoverDescriptionProps. Source: src/popover/popover.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<Record<string, never>> | – |
- Also takes every attribute of
<p>exceptid. (Omit<ComponentPropsWithRef<"p">, "id">)
render takes an element, or, in client code, a function of the props and no state.
Popover.Close
With render or children, the consumer's button. With neither, an IconButton (tertiary, sm) showing the core close glyph, named by labels.close: the IconButton element's markup written out, as Dialog.Close does, so the Popover entry does not pull in IconButton's tooltip chain.
Client component Renders data-slot="popover-close" with the class nz-popover-close. Props: PopoverCloseProps. Source: src/popover/popover.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | Text content, rendered in a plain |
labels | Partial<Pick<PopoverLabels, "close">> | – | The default IconButton's accessible name; an |
render | RenderProp<PopoverCloseState> | – | Must render a |
- 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 (PopoverCloseState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
focusVisiblerequired | boolean | – | |
pressedrequired | boolean | – |
Other exports
PopoverClosePropstypeimport type { PopoverCloseProps } from '@nizivo/react/popover'The props of
Popover.Close, listed above.PopoverCloseStatetypeimport type { PopoverCloseState } from '@nizivo/react/popover'The render state of
Popover.Close, listed above.PopoverDescriptionPropstypeimport type { PopoverDescriptionProps } from '@nizivo/react/popover'The props of
Popover.Description, listed above.PopoverLabelstypeimport type { PopoverLabels } from '@nizivo/react/popover'Strings the Popover renders (§9.3 rule 7, §16.4).
closeis the accessible name of the defaultPopover.Close(an IconButton showing the close glyph), set through that part'slabels. Engine-owned with React Aria (not overridable):dismiss, the visually hidden "Dismiss" buttons at both ends of the popup.Name Type Default Description closerequiredstring'Close'dismissrequiredstring– PopoverPopupPropstypeimport type { PopoverPopupProps } from '@nizivo/react/popover'The props of
Popover.Popup, listed above.PopoverPopupStatetypeimport type { PopoverPopupState } from '@nizivo/react/popover'Name Type Default Description enteringrequiredboolean– exitingrequiredboolean– siderequiredSide"top""right""bottom""left"– PopoverRootPropstypeimport type { PopoverRootProps } from '@nizivo/react/popover'The props of
Popover.Root, listed above.PopoverSidetypeimport type { PopoverSide } from '@nizivo/react/popover'Logical side; the resolved physical side is
data-sideon the popup.type PopoverSide = "top" | "bottom" | "start" | "end"PopoverTitleLeveltypeimport type { PopoverTitleLevel } from '@nizivo/react/popover'type PopoverTitleLevel = 2 | 3 | 4 | 5 | 6PopoverTitlePropstypeimport type { PopoverTitleProps } from '@nizivo/react/popover'The props of
Popover.Title, listed above.PopoverTriggerPropstypeimport type { PopoverTriggerProps } from '@nizivo/react/popover'The props of
Popover.Trigger, listed above.PopoverTriggerStatetypeimport type { PopoverTriggerState } from '@nizivo/react/popover'The render state of
Popover.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 |
|---|---|---|
icon-button-icon.nz-icon-button-icon | Popover.Closeinner element (CSS only) | none |
popover-close.nz-popover-close | Popover.Closeroot (takes render); interactive, focus: self |
|
popover-content.nz-popover-content | Popover.Popupinner element (CSS only) | none |
popover-description.nz-popover-description | Popover.Descriptionroot (takes render) | none |
popover-popup.nz-popover-popup | Popover.Popuproot (no render); forced colours: border |
|
popover-title.nz-popover-title | Popover.Titleroot (takes render) | none |
popover-trigger.nz-popover-trigger | Popover.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-popover-bg | --nz-bg-surface-raised |
|
--nz-popover-border | --nz-border-subtle |
|
--nz-popover-radius | --nz-radius-lg |
|
--nz-popover-shadow | --nz-elevation-3 |
|
--nz-popover-width | max-content |
|
Layers: nz.components. Classes: .nz-hit-target, .nz-icon-button, .nz-popover-close, .nz-popover-content, .nz-popover-description, .nz-popover-popup, .nz-popover-title, .nz-popover-trigger. Runtime variables: --nz-available-height, --nz-transform-origin.
Tokens this stylesheet reads (22)
--nz-bg-surface-raised, --nz-border-subtle, --nz-border-width-default, --nz-duration-fade, --nz-duration-spring, --nz-easing-enter, --nz-easing-exit, --nz-easing-spring, --nz-elevation-3, --nz-font-body-m, --nz-font-heading-h4, --nz-icon-secondary, --nz-radius-lg, --nz-scale-enter, --nz-space-2, --nz-space-4, --nz-space-6, --nz-text-primary, --nz-text-secondary, --nz-tracking-body-m, --nz-tracking-heading-h4, --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:
popover-close: draws the focus outline itself on keyboard focus.popover-trigger: draws the focus outline itself on keyboard focus.
In forced-colours mode these states keep a visible system colour: popover-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 |
|---|---|
| Enter, Space | On the trigger (a button), opens the popover and moves focus to its first control (the close button in the corner is passed over), or to the popover itself when it has none. An element with autoFocus wins. |
| Tab, Shift+Tab | Moves focus between the controls inside the popover; focus stays inside while it is open. |
| Escape | Closes the popover and returns focus to the trigger. With a Select, Menu or Tooltip open inside it, Escape closes that one first. |
Source: react-aria-components 1.22.1 DialogTrigger, Popover and Dialog (react-aria 3.53.1 usePopover, which contains focus while open) through _engines/react-aria/popover.client.tsx. The first-control rule and focus return are Nizivo's (ARCHITECTURE.md §12.2), checked by the overlay lane (test/overlay/popover.spec.tsx, nesting-popover-drawer.spec.tsx) in Chromium, Firefox and WebKit; apps/docs/scripts/smoke.ts checks opening with Enter, Escape closing and focus back on the trigger.
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: none
PointerFx
A card that tilts toward a mouse pointer and lifts, or a button that leans toward it. Decorative, mouse only, and still under reduced motion. Also usePointerFx, for an element of your own in a client component.
ProgressBar
A native progress bar for a task or goal, determinate or indeterminate, with a visible label and value.