Popover
StableA raised sheet of free-form content anchored to a trigger: a quick edit, a few filters, or the details behind a value. It sits beside what it changes and the page stays live around it.
Basics
Wrap the button in PopoverTrigger asChild and put anything in PopoverContent. It shares the menu's sheet (14px corners, the floating shadow) and its motion: it grows out of its trigger on a spring and leaves faster than it came. Here, adjusting stock from a product row.
In both themes
Held open for review, light beside dark. In light the sheet is white on the floating shadow; in dark it lifts to graphite with a faint ring so its edge holds on a dark page. Fields inside it keep their own wells.
Open them for real
Each trigger opens its popover in the theme of its own side.
Padding
The content decides. md (16px, the default) suits forms. sm (12px) suits compact details like a customer card. none is for lists and tables that bring their own padding, so their rows and dividers run edge to edge.
Width
By default the sheet fits its content. Pass width in pixels (or any CSS length) when the content would otherwise jump about as it changes, as a form does. 'trigger' matches the trigger, or the anchor when there is one (see Anchor). On a small screen the sheet never grows wider than the space it has.
Closing
A popover closes when you click outside it, press Escape, or press its trigger again. Inside, wrap any button that should close it in PopoverClose asChild: a Cancel, a Done, or a close button in the corner. To close it after an action, control open, as the stock example does after saving.
Anchor
PopoverAnchor positions the sheet against something other than the trigger. Here the pencil opens it, but it lines up with the whole price row, and width="trigger" makes it as wide as the row.
Popover, menu or dialog
Four overlays, four jobs. Pick by what is inside and how much the moment matters.
| Use | For | How it behaves |
|---|---|---|
| Menu | A list of actions, or one quick choice such as a sort order. | Rows only. Arrow keys and typing move between rows; choosing one closes it. |
| Popover | A small amount of free-form content beside what it changes: a quick edit, a few filters, the details behind a value. | The page stays live. Clicking outside or Escape closes it, and unsaved input goes with it. |
| Dialog | A task that must be finished or dismissed before carrying on: several fields, a question before something that matters. | Dims the page, traps focus and locks scrolling until it closes. |
| Tooltip | A short label for a control, on hover or focus. | Never holds anything to press or type into. |
- Keep it small: one short task or a few details. If it needs to scroll, or more than three or four fields, use a Dialog.
- Save with a button when the change matters. A click outside closes the popover and loses what was typed.
- Give it a heading and point
aria-labelledbyat it, or passaria-label. - Open one popover at a time, and never one from inside another.
- Keep it next to the thing it changes. For an edit to a row, open it from that row.
Accessibility
- Built on Radix Popover. The trigger carries
aria-haspopup="dialog"andaria-expanded, and the sheet is adialog. It has no name of its own: label it witharia-labelledbyoraria-label. - When it opens, focus moves to the first thing inside that takes focus. Tab moves through it and on to the page after it.
- Escape closes it and hands focus back to the trigger. A click outside closes it and leaves focus where you clicked.
- Pass
modalto the root to keep focus inside, block the page behind and hide it from screen readers while the popover is open. - With reduced motion the sheet fades in and out without growing.
Props
Every part also accepts the matching Radix Popover props.
Popover
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | None | Controlled open state. |
defaultOpen | boolean | false | Starting state when uncontrolled. |
onOpenChange | (open: boolean) => void | None | Called when it opens or closes. |
modal | boolean | false | Keep focus inside and block the page behind while open. |
PopoverTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render your Button as the trigger. Almost always on. |
PopoverContent
| Prop | Type | Default | Description |
|---|---|---|---|
padding | 'none' | 'sm' | 'md' | 'md' | Inner padding: 0 for lists that bring their own, 12px, or 16px. |
width | number | string | 'trigger' | None | Fixed width in px, any CSS length, or 'trigger' to match the trigger (or the anchor). By default it fits its content. |
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Preferred side. Flips when there is no room. |
align | 'start' | 'center' | 'end' | 'center' | Which part of the trigger it lines up with. |
sideOffset | number | 8 | Gap from the trigger in px. |
alignOffset | number | 0 | Nudge along the aligned edge in px. |
collisionPadding | number | 8 | Space kept from the edges of the window. |
container | HTMLElement | null | document.body | Where the sheet is portalled. |
onOpenAutoFocus | (event: Event) => void | None | Runs as focus moves in. Prevent it to keep focus on the trigger. |
onCloseAutoFocus | (event: Event) => void | None | Runs as focus goes back to the trigger. Prevent it to send focus elsewhere. |
PopoverClose
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render your button as the close control. |
PopoverAnchor
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render your element as the anchor the sheet is positioned against. |