Popover
Non-modal content anchored to a trigger: a small details panel, a definition, or the touch stand-in for a tooltip.
import {
Popover,
PopoverArrow,
PopoverClose,
PopoverDescription,
PopoverPopup,
PopoverSource,
PopoverTitle,
PopoverTrigger,
} from '@fairgarden-private/design/components/Popover'
The panel renders in a portal as a white scope that follows the page mode, whatever the trigger's ground. Its --border-size-2 --primary12 frame carries the boundary in both modes, with --ds-radius-8 and no shadow. It sits 8 px from the trigger, aligned to its start edge, and flips before it would clip. It is 240 to 360 px wide from 768 px, and the viewport less its margins below. It opens on press, never on hover, instantly or with a clip reveal from the trigger side; Esc or an outside press closes it. Popovers are hidden in print.
Keep essential instructions out of a popover. A filter panel with more than two controls, or anything that needs confirmation, belongs in a Dialog.
Panel and definition
Panel and definition
kind="panel" (the default) is a padded panel with a title in type-subhead; put PopoverClose first so the title wraps beside it. kind="definition" sets the term in tracked caps, the definition in type-body-ui and an optional PopoverSource caption. PopoverArrow adds the tail, whose edge continues the frame.
'use client'
import {
Popover,
PopoverArrow,
PopoverClose,
PopoverDescription,
PopoverPopup,
PopoverSource,
PopoverTitle,
PopoverTrigger,
} from '@fairgarden-private/design/components/Popover'
import styles from './kinds.module.css'
export function PopoverKinds() {
return (
<div className={styles.row}>
<Popover>
<PopoverTrigger variant="outline">Trail Details</PopoverTrigger>
<PopoverPopup>
<PopoverArrow />
<PopoverClose />
<PopoverTitle>Ridge Loop</PopoverTitle>
<PopoverDescription>
6.4 km with 310 m of climbing. Open dawn to dusk; dogs stay on a leash.
</PopoverDescription>
</PopoverPopup>
</Popover>
<Popover>
<PopoverTrigger variant="text" icon="help" iconPosition="end">
Riparian Buffer
</PopoverTrigger>
<PopoverPopup kind="definition">
<PopoverArrow />
<PopoverTitle>Riparian buffer</PopoverTitle>
<PopoverDescription>
The strip of native plants along a stream that filters runoff and shades the water.
</PopoverDescription>
<PopoverSource>State Watershed Guide, 2024</PopoverSource>
</PopoverPopup>
</Popover>
</div>
)
}
API Reference
Popover and PopoverTrigger
Groups the parts of a popover (Base UI Popover.Root). Esc or an outside press closes it.
Opens the popover on press, never on hover (§10.15). Renders a Button (§9.2); its data-popup-open shows the expanded state.
| Prop | Type | Description |
|---|---|---|
butted | | Butts the button against an adjacent field on its |
handle | | Associates a detached trigger with a Popover created by |
icon | | One optional functional glyph (§6.10), inline tier, FILL 0. |
iconOnly | |
|
iconPosition | | Which side of the label the glyph sits on (a |
onMedia | | Only with |
onPress | | Called on click, after |
payload | | A payload handed to the Popover’s children function when this trigger opens it. |
primary | | Primary Radix scale, from the primary roster: the outline edge, labels and focus ring. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the |
size | | Fixed height: |
variant | |
|
children | | The label: verb plus object, authored in title case [D160]. |
PopoverPopup and PopoverArrow
The anchored panel, rendered in its Base UI Portal as a nested white
scope: --primary1 face, --border-size-2 --primary12 frame, --ds-radius-8,
240–360 px wide from --md-n-above and the viewport less its margins
below. It opens instantly or with a clip reveal from the trigger side.
Render PopoverArrow inside it for the tail.
| Prop | Type | Description |
|---|---|---|
kind | |
|
primary | | Primary Radix scale inside the popup’s |
secondary | | Secondary Radix scale inside the popup: link underlines and other accents only. |
container | | The element the portal renders into. Default: |
align | | Alignment to the trigger. Default |
alignOffset | | Offset along the alignment axis in px. Default 0. |
side | | Which side of the trigger the panel sits on. Default |
sideOffset | | Distance from the trigger in px. Default 8 ( |
collisionPadding | | Clearance from the viewport edge in px before the panel shifts or flips. Default 16. |
keepMounted | | Keeps the portal mounted while closed. |
The tail: a --ds-space-12 × 6 px triangle filled with the face, its --border-size-2 --primary12 edge continuing the panel’s frame.
PopoverTitle, PopoverDescription and PopoverSource
The popover’s heading (Base UI Popover.Title, an h2) and accessible
name. In the definition kind it is the term, in tracked caps
(type-label); in a panel, type-subhead.
The content or the definition (Base UI Popover.Description), in type-body-ui --primary12.
A definition’s source caption, in type-caption --role-muted.
PopoverClose
The optional close X, top end: an icon-only Button with the inline-tier
close glyph and a --ds-size-hit target (§9.2). Pass render to close
from another control instead; label is then ignored.
| Prop | Type | Description |
|---|---|---|
label | | The close X’s accessible name, visually hidden. Default “Close”. |
Additional types
PopoverArrowProps
Props for PopoverArrow: Base UI Popover.Arrow props.
type PopoverArrowProps = PopoverArrow.PopoverArrowPropsPopoverCloseProps
Props for PopoverClose: Base UI Popover.Close props plus the X’s accessible name.
type PopoverCloseProps = PopoverCloseProps & { label?: string }PopoverDescriptionProps
Props for PopoverDescription: Base UI Popover.Description props.
type PopoverDescriptionProps = PopoverDescription.PopoverDescriptionPropsPopoverKind
type PopoverKind = 'panel' | 'definition'<'panel' | 'definition' | null | undefined>PopoverPopupProps
Props for PopoverPopup: Base UI Popover.Popup props plus placement, kind, color axes and portal options.
type PopoverPopupProps = PopoverPopupProps & {
kind?: 'panel' | 'definition' | null;
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
side?: Side;
align?: Align;
sideOffset?: number | OffsetFunction;
alignOffset?: number | OffsetFunction;
collisionPadding?: Padding;
container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null;
keepMounted?: boolean;
}PopoverProps
Props for Popover: Base UI Popover.Root props (open, onOpenChange, modal …).
type PopoverProps<Payload = unknown> = PopoverRoot.Props<Payload>PopoverSourceProps
Props for PopoverSource: p props and render.
type PopoverSourceProps = useRender.ComponentProps<'p'>PopoverTitleProps
Props for PopoverTitle: Base UI Popover.Title props.
type PopoverTitleProps = PopoverTitle.PopoverTitlePropsPopoverTriggerProps
Props for PopoverTrigger: Button props plus Base UI’s trigger options.
type PopoverTriggerProps = (
| {
iconOnly?: false | null;
onMedia?: false | null;
size?: 'sm' | 'md' | 'lg' | 'xl' | null;
icon?:
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail';
children?: React.ReactNode;
}
| {
iconOnly: true;
onMedia?: boolean | null;
size?: 'sm' | 'md' | 'lg' | null;
icon:
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail';
children: React.ReactNode;
}
) & {
/**
* `outline` (default) is the outline twin; `solid` is the page's primary
* action, filled with the scope's action scale; `text` is a text button
* whose glyph trails (§9.2).
*/
variant?: 'text' | 'solid' | 'outline' | null;
/**
* Butts the button against an adjacent field on its `start` or `end`
* edge, as in the butted submit. Default: none.
*/
butted?: 'start' | 'end' | null;
/**
* Primary Radix scale, from the primary roster: the outline edge, labels
* and focus ring. Never defaulted; omitted, it inherits the scope [D133].
*/
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/**
* Secondary Radix scale: the `solid` fill and the `text` glyph. Omitted,
* `solid` falls back to the scope's action scale.
*/
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
/** Which side of the label the glyph sits on (a `text` Button's chevron trails). */
iconPosition?: 'start' | 'end';
/**
* Called on click, after `onClick`.
* @deprecated Use `onClick`. Kept as an alias from the react-aria Button.
*/
onPress?: PressCallback;
/** Associates a detached trigger with a Popover created by `Popover.createHandle`. */
handle?: PopoverHandle<unknown>;
/** A payload handed to the Popover's children function when this trigger opens it. */
payload?: unknown;
}Specification: DESIGN-SYSTEM.md §10.15 (popover) and §10.1 (the overlay surface).