Overview
<Popover /> is the canonical interactive floating-panel primitive — Tooltip's focusable cousin.
Where Tooltip is read-only and pointer-events: none, Popover does receive focus, can host
arbitrary interactive content (forms, lists, buttons), and participates in the engine's full
overlay lifecycle: positioning, portaling, focus trapping, escape stack, outside-click.
It is the first compound overlay in the DS — Popover.Trigger + Popover.Content +
Popover.Arrow + Popover.Close — and the canonical shape Menu, Select, Combobox will copy.
Overview — account menu trigger + open panel
When to use
- A small floating panel of related actions: "More options" → 3-button menu.
- An inline form fragment (subscribe, share, set status).
- A "click to expand" detail card — link previews, hover-cards (with
openOnHoverlater). - "Are you sure?" confirmation patterns near the destructive trigger.
- Notification trays, account menus.
When NOT to use
- The content is purely a hint with no interactivity → use
<Tooltip />. - The content is critical and blocks the page → use
<Modal />. - The content is a list of actions → use
<Menu />. - The content is a
<select>-style chooser → use<Select />or<Combobox />.
Anatomy
<Popover>
<Popover.Trigger> button </Popover.Trigger>
<Popover.Content>
arbitrary content
<Popover.Arrow /> (optional)
<Popover.Close /> (optional)
</Popover.Content>
</Popover><Popover>
<Popover.Trigger> button </Popover.Trigger>
<Popover.Content>
arbitrary content
<Popover.Arrow /> (optional)
<Popover.Close /> (optional)
</Popover.Content>
</Popover>Popover— context provider. Ownsopen,setOpen, the trigger and floating refs, and the lifecycle hooks (useEscapeStack,useOutsideClick).Popover.Trigger— clones a single child (asChild, default), attaching ARIA + click handlers. Or render an inline<button>whenasChild={false}.Popover.Content— the portal-rendered, positioned, focus-trapped, animated surface. Carries the visual axes (variant/size/color/placement/offset/showArrow).Popover.Arrow— optional SVG arrow positioned by Floating UI's middleware.Popover.Close— built-in × button in the corner; callssetOpen(false).
Variants
Three variants of the Content surface — all elevated (shadow-lg), all rounded:
solid— paper background + neutral border. Default. Color-neutral;coloris ignored.outline— paper background + 1px colored border. Brand-aligned popovers.soft— subtle tinted background + low-opacity colored border. Editorial popovers.
The 3 variants × 7 colors compound matrix has 14 active cells (solid ignores color, outline
and soft use one row each). Adding a new color = palette entry + 2 compound rows.
Sizes
| Size | Padding | Min-width | Max-width | Arrow size |
|---|---|---|---|---|
sm | p-3 | min-w-48 | max-w-xs | 12×6 |
md | p-4 | min-w-56 | max-w-sm | 16×8 |
lg | p-6 | min-w-72 | max-w-md | 20×10 |
Placement
Same 12-placement vocabulary as Tooltip (top-start / top / top-end / right-start / …).
Floating UI's flip middleware automatically swaps to the opposite side at viewport edges; the
post-flip placement is reflected on the floating element via data-placement.
Behavior
| Prop | Default | Effect |
|---|---|---|
modal | false | When true, adds a backdrop + aria-modal="true" + aria-haspopup="dialog". Use sparingly. |
trapFocus | true | Focus moves into Content on open; Tab cycles inside; focus returns to trigger on close. |
closeOnEscape | true | Esc closes the topmost popover (escape-stack ordering). |
closeOnOutsideClick | true | Pointer-down outside trigger + content closes the popover. |
showArrow (on Content) | false | Popovers feel like panels, not speech bubbles. Default off. |
offset (on Content) | 8 | Px gap between trigger and content. Tooltip uses 6; Popover is slightly more spacious. |
Accessibility
- Trigger carries
aria-haspopup('true'for non-modal,'dialog'for modal),aria-expanded,aria-controls={contentId}while open, anddata-state="open"|"closed". - Content carries
role="dialog",aria-labelledby={triggerId}, and (for modal)aria-modal="true". - Focus management (when
trapFocus={true}):- On open: focus moves to
initialFocusif provided, else first focusable child, else Content itself (which carriestabIndex={-1}). - Tab cycles inside Content via the engine's
useFocusTrap. - On close: focus returns to the trigger.
- On open: focus moves to
- Escape closes via the engine's
useEscapeStack— only the topmost popover closes per press, so nested popovers unwind cleanly. - Outside click uses
pointerdown(notclick) for capture-phase responsiveness — same approach Radix uses. - Close button carries
aria-label="Close"by default; override viaaria-labelprop. - axe-core: zero violations across the full variant × color × open-state matrix.
Examples
Basic
Variants
Sizes
Colors
Placements
WithArrow
WithCloseButton
ConfirmAction
FormInside
NestedPopover
Controlled
ModalPopover
Theming
defineTheme({
components: {
Popover: {
defaultProps: { /* root behavior props — modal/trapFocus/etc. */ },
styleOverrides: {
content: 'shadow-xl',
arrow: '',
close: 'text-fg-default',
backdrop: 'bg-fg-default/40',
},
},
},
});defineTheme({
components: {
Popover: {
defaultProps: { /* root behavior props — modal/trapFocus/etc. */ },
styleOverrides: {
content: 'shadow-xl',
arrow: '',
close: 'text-fg-default',
backdrop: 'bg-fg-default/40',
},
},
},
});Per-instance overrides via <Popover.Content className sx style /> merge on top of the theme
overrides, which merge on top of the recipe — same precedence as everywhere else in the DS.