Skip to content
Folio

Overlays

Popover

A panel anchored to a control, holding content you can interact with.

Usage

When to reach for it

Anything with a link, a field or a button in it. A tooltip describes and cannot be entered — put a control inside one and it becomes unreachable.
TSX
import { Popover } from '@misoto22/design'

Notes

Radix Popover root, trigger, anchor and close, as typed passthroughs.

Anatomy

Popover anatomy
ElementDescription
TriggerrequiredPopoverTrigger, a passthrough. Pass asChild to keep your own control; Radix returns focus here when the panel closes on Escape.
PanelrequiredThe anchored dialog: a flat 18rem wide with 1rem of padding, 8px off the trigger, named by label. Focus moves into it on open, but it is not trapped there.
AnchorPopoverAnchor, for when the panel should be positioned against something other than the control that opened it — the row an overflow button acts on, a selection in text.
ClosePopoverClose, and the 32px X that showClose renders in the top-end corner. Off by default, unlike Dialog’s.

Best practices

Do

  • Make label say what the panel holds rather than echoing the trigger: it is announced on entry, so “Filter options” tells a reader where they have landed where a repeat of the button text tells them nothing new.
  • Use PopoverAnchor when the visual anchor is not the trigger — a toolbar button acting on a selected row — otherwise the panel tracks the button and drifts away from the thing it is editing.
  • Wrap the dismissing control in PopoverClose rather than flipping your own state, so the close runs through Radix and focus goes back to the trigger instead of to the top of the document.
  • Turn showClose on when the panel holds a form. It is off by default, and a non-modal panel whose only exit is clicking away gives an in-progress edit no deliberate end.

Don’t

  • It is not modal — Radix defaults modal to false and nothing here changes that, so there is no focus trap and no scroll lock: tabbing past the last control inside moves focus into the page, which Radix reads as a focus-outside and closes the panel mid-task.
  • A list of actions belongs in a DropdownMenu. A popover’s contents are ordinary tab stops, so ten actions is ten stops with no type-ahead, where a menu is one stop with arrow keys and a letter jump inside it.
  • The panel sits at --z-dropdown, which resolves to 220 — above a Dialog’s 210, so that a popover opened FROM a dialog is reachable. A popover the page opened and your own state holds open therefore paints over a modal that arrives afterwards; Radix closes it on the interaction outside, and a controlled open that ignores that is the one way to see it.

Examples

a filter panel

A panel with controls in it, which is the whole distinction from a tooltip: put a field or a button inside a tip and it is unreachable. label names the panel and is announced on entry, so make it say what the panel holds rather than repeating the trigger. showClose is off by default and turned on here because the panel holds a form — a non-modal panel whose only exit is clicking away gives an in-progress edit no deliberate end. It really is not modal: there is no focus trap, so tabbing past the last control moves focus into the page, which Radix reads as a focus-outside and closes the panel.

anchored elsewhere

The panel is positioned against the row it edits, not against the toolbar button that opened it. That is what PopoverAnchor is for: without it the panel tracks the trigger and drifts away from the thing being changed, which on a long table means an editor floating at the top of the screen over a row near the bottom. The trigger stays the trigger — focus still returns to it on Escape — only the geometry moves.

Selected row

codex/photo-cache62s

panel or menu

The two anchored panels, side by side, on the one question that separates them: is the content something you fill in, or something you pick? A popover's contents are ordinary tab stops, so four actions in one is four stops with no type-ahead and no arrow keys — where a menu is a single stop with both. A menu, in turn, cannot hold a slider or a field, because a menuitem is not a place a value gets entered. Both portal to the same layer and both close on Escape back to their trigger; neither belongs inside a Dialog, which paints over them.

Parts

Composed at the call site rather than configured through props, so a layout this component did not anticipate is still expressible.

PopoverContent

A panel anchored to a control, holding content the reader can interact with.

The line against Tooltip is not visual, it is behavioural: a tooltip describes and cannot be entered; a popover holds things you tab to. Anything with a link, a field or a button in it is a popover, and putting that inside a tooltip makes it unreachable — the tooltip closes as soon as focus tries to move into it.

Against DropdownMenu: a menu is a list of actions with menu semantics and arrow-key navigation. A popover is free-form, and its contents Tab like the rest of the page.

PopoverContent props
PropTypeDefaultDescription
labelrequiredstringNames the panel for assistive tech. Required — a popover is a dialog.
showClosebooleanfalseShow the top-end close control.

Also accepts everything in ComponentProps<typeof PopoverPrimitive.Content>. Those are forwarded to the underlying element and are not listed row by row.

Re-exports

Popover = PopoverPrimitive.Root

Radix Popover root, trigger, anchor and close, as typed passthroughs.

PopoverTrigger = PopoverPrimitive.Trigger
PopoverAnchor = PopoverPrimitive.Anchor
PopoverClose = PopoverPrimitive.Close

Keyboard

Popover keyboard interactions
KeyDoes
EnterSpaceOpens it.
TabMoves through its contents like the rest of the page.
EscapeCloses it and returns focus to the trigger.

Accessibility

  • Inside a bounded frame — a device preview, an embedded console — wrap the subtree in `<OverlayContainer container={el}>`. The panel then renders into that element and collides with its edges instead of the viewport’s, and inherits the `dir` and `data-density` set there.
  • label is required: a popover is a dialog, and an unnamed one announces nothing.
  • Its contents Tab like the rest of the page, unlike a menu’s arrow-key list.