Popover
Displays rich content in a portal, triggered by a button.
Installation
Install the following dependencies:
Set up the token layer and the StyleX compiler, if you have not already. See
Installation, and paste states.ts and state-styles.ts from the same page.
states.ts holds the shared opaque surface that the popup box, the dialog and
the hover card all use. state-styles.ts reads the popup's transition state.
Copy and paste the following code into your project.
Update the import paths to match your project setup.
Usage
Composition
PopoverTrigger renders a <button>. Pass render to use another element as
the trigger, such as a Button variant.
The popup is a column with a 0.625rem gap, so PopoverHeader and whatever
follows it need no margins of their own.
Basic
PopoverHeader stacks PopoverTitle over PopoverDescription. Base UI wires
both to the popup's aria-labelledby and aria-describedby.
Align
Use align on PopoverContent to slide the popup along the trigger's edge, and
side to pick which edge it opens from. Base UI flips the side when there is no
room.
With form
Put controls straight in the popup. PopoverClose is a button that closes it,
so a cancel and a confirm both go through the same part.
A popover is not modal by default. The page keeps scrolling and a click outside
closes it. Pass modal to Popover to lock the page instead, and render a
PopoverClose inside the popup. Base UI only traps focus when it finds one, so
without it a keyboard user can tab back out of a locked page.
API reference
Pass render to replace a part's element or compose it with another component.
Every part wraps the Base UI Popover part of the same name and accepts its
props. See the Base UI reference
for the full list. Each styled part also takes xstyle and className.
Popover
| Prop | Type | Default |
|---|---|---|
open | boolean | — |
defaultOpen | boolean | false |
onOpenChange | (open: boolean, details) => void | — |
modal | boolean | "trap-focus" | false |
handle | PopoverHandle | — |
PopoverTrigger
| Prop | Type | Default |
|---|---|---|
openOnHover | boolean | false |
delay | number | 300 |
closeDelay | number | 0 |
payload | Payload | — |
delay and closeDelay do nothing without openOnHover.
PopoverContent
| Prop | Type | Default |
|---|---|---|
side | "top" | "bottom" | "left" | "right" | "bottom" |
sideOffset | number | 4 |
align | "start" | "center" | "end" | "center" |
alignOffset | number | 0 |
The popup is 18rem wide and clamped to --available-width, so it narrows
rather than overflowing a phone-width viewport. Change the width through
xstyle.
Data attributes
| Attribute | On | Effect |
|---|---|---|
data-slot="popover-content" | the popup | Popup surface |
data-side, data-align | the popup | Which way it slides in from |
data-starting-style, data-ending-style | the popup | Base UI writes them while the popup animates |
data-open, data-closed | both | Base UI writes them on the trigger and the popup |