Popover
Popovers are interactive floating elements that display rich content, anchored to a trigger element when a user clicks or optionally hovers over it.
Introduction
Popovers can contain arbitrary rich interactive content similar to a Dialog. The main difference is that a Popover can be anchored to another element on the page and is non-modal by default, without a backdrop or required close button.
Installation
Base UI components are all available as a single package.
npm install @base-ui-components/react
Once you have the package installed, import the component.
import { Popover } from '@base-ui-components/react/Popover';
Anatomy
Popover is implemented using a collection of related components:
<Popover.Root />
is a top-level component that wraps the other components.<Popover.Trigger />
renders the trigger element.<Popover.Backdrop />
renders an optional backdrop element behind the popup.<Popover.Positioner />
renders the popover's positioning element.<Popover.Popup />
renders the popover popup itself.<Popover.Arrow />
renders an optional pointing arrow, placed inside the popup.<Popover.Title />
renders an optional title heading element, placed inside the popup.<Popover.Description />
renders an optional description element, placed inside the popup.<Popover.Close />
renders an optional close button, placed inside the popup.
<Popover.Root>
<Popover.Trigger />
<Popover.Backdrop />
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title />
<Popover.Description />
<Popover.Close />
</Popover.Popup>
</Popover.Positioner>
</Popover.Root>
Placement
By default, the popover is placed on the bottom side of its trigger, the default anchor. To change this, use the side
prop:
<Popover.Root>
<Popover.Trigger />
<Popover.Positioner side="right">
<Popover.Popup>Popover</Popover.Popup>
</Popover.Positioner>
</Popover.Root>
You can also change the alignment of the popover in relation to its anchor. By default, it is centered, but it can be aligned to an edge of the anchor using the alignment
prop:
<Popover.Positioner side="right" alignment="start">
<Popover.Popup>Popover</Popover.Popup>
</Popover.Positioner>
Due to collision detection, the popover may change its placement to avoid overflow. Therefore, your explicitly specified side
and alignment
props act as "ideal", or preferred, values.
To access the true rendered values, which may change as the result of a collision, the popup element receives data attributes:
// Rendered HTML (simplified)
<div>
<div data-side="left" data-alignment="end">
Popover
</div>
</div>
This allows you to conditionally style the popover based on its rendered side or alignment.
Offset
The sideOffset
prop creates a gap between the anchor and popover popup, while alignmentOffset
slides the popover popup from its alignment, acting logically for start
and end
alignments.
<Popover.Positioner sideOffset={10} alignmentOffset={10}>
Hover
To open the popover on hover instead of click for pointer users, which enables creating tooltip-like popovers that may contain interactive content, add the openOnHover
prop:
<Popover.Root openOnHover>
Delay
To change how long the popover waits until it opens or closes when openOnHover
is enabled, use the delay
and closeDelay
props, which represent how long the popover waits after the cursor rests on the trigger to open, or moves away from the trigger to close, in milliseconds:
<Popover.Root openOnHover delay={200} closeDelay={200}>
Controlled
To control the popover with external state, use the open
and onOpenChange
props:
function App() {
const [open, setOpen] = React.useState(false);
return (
<Popover.Root open={open} onOpenChange={setOpen}>
{/* Subcomponents */}
</Popover.Root>
);
}
Arrow
To add an arrow (caret or triangle) inside the popover content that points toward the center of the anchor element, use the Popover.Arrow
component:
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
Popover
</Popover.Popup>
</Popover.Positioner>
It automatically positions a wrapper element that can be styled or contain a custom SVG shape.
Close
Since popovers are non-modal, a close button is not required — users can escape the popover at any time. However, a close button may still be rendered inside the popup to give additional ability to close the popover:
<Popover.Popup>
<Popover.Close />
</Popover.Popup>
Title and description
To optionally label and describe the popover with wide screen reader support, use the Title
and Description
components:
<Popover.Popup>
<Popover.Title>My popover</Popover.Title>
<Popover.Description>A description for my popover.</Popover.Description>
</Popover.Popup>
Backdrop
You may dim content behind the popover in order to draw more attention to it by rendering an optional backdrop. This blocks pointer events behind the popover by default as well, meaning two clicks are required to click items behind the popover, as the first click triggers the popover to be dismissed.
<Popover.Root>
<Popover.Backdrop />
{/* Subcomponents */}
</Popover.Root>
It has the same maximum z-index
as the Positioner
component by default, and should be placed before it in the React tree. This allows it to block all content behind it, and also be independently animated.
Hover
When combining openOnHover
with the Backdrop component, ensure the Backdrop does not block pointer events:
<Popover.Backdrop style={{ pointerEvents: 'none' }} />
This will prevent the Popup from closing unexpectedly.
If the Backdrop is colored, then you may want to ensure the trigger element can appear over the top of it, so it acts more like a mask. To do so, ensure the z-index layering is correct:
<Popover.Trigger style={{ position: 'relative', zIndex: 1 }} />
Anchoring
By default, the Trigger
acts as the anchor, but this can be changed to another element.
- A DOM element (stored in React state):
<Popover.Positioner anchor={anchorNode}>
- A React ref:
<Popover.Positioner anchor={anchorRef}>
- A virtual element object, consisting of a
getBoundingClientRect
method and an optionalcontextElement
property:
<Popover.Positioner
anchor={{
getBoundingClientRect: () => DOMRect,
// `contextElement` is an optional but recommended property when `getBoundingClientRect` is
// derived from a real element, to ensure collision detection and position updates work as
// expected in certain DOM trees.
contextElement: domNode,
}}
>
Styling
The Popover.Positioner
element receives the following CSS variables, which can be used by Popover.Popup
:
--anchor-width
: Specifies the width of the anchor element. You can use this to match the width of the popover with its anchor.--anchor-height
: Specifies the height of the anchor element. You can use this to match the height of the popover with its anchor.--available-width
: Specifies the available width of the popup before it overflows the viewport.--available-height
: Specifies the available height of the popup before it overflows the viewport.--transform-origin
: Specifies the origin of the popup element that represents the point of the anchor element's center. When animating scale, this allows it to correctly emanate from the center of the anchor.
Large content
If your popover is large enough that it cannot fit inside the viewport (especially on small or narrow screens as on mobile devices), the --available-width
and --available-height
properties are useful to constrain its size to prevent it from overflowing.
.PopoverPopup {
max-width: var(--available-width);
max-height: var(--available-height);
overflow: auto;
}
The overflow: auto
property will prevent the Arrow
from appearing, if specified. You can instead place this on a wrapper child inside the Popup
:
<Popover.Popup className="PopoverPopup">
<Popover.Arrow />
<div className="PopoverPopup-content">Large content</div>
</Popover.Popup>
.PopoverPopup-content {
max-width: var(--available-width);
max-height: var(--available-height);
overflow: auto;
}
Absolute maximums can also be specified if the popover's size can be too large on wider or bigger screens:
.PopoverPopup-content {
max-width: min(500px, var(--available-width));
max-height: min(500px, var(--available-height));
overflow: auto;
}
Animations
The popover can animate when opening or closing with either:
- CSS transitions
- CSS animations
- JavaScript animations
CSS transitions
Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
<Popover.Popup className="PopoverPopup">Popover</Popover.Popup>
.PopoverPopup {
transform-origin: var(--transform-origin);
transition-property: opacity, transform;
transition-duration: 0.2s;
/* Represents the final styles once exited */
opacity: 0;
transform: scale(0.9);
}
/* Represents the final styles once entered */
.PopoverPopup[data-open] {
opacity: 1;
transform: scale(1);
}
/* Represents the initial styles when entering */
.PopoverPopup[data-entering] {
opacity: 0;
transform: scale(0.9);
}
Styles need to be applied in three states:
- The exiting styles, placed on the base element class
- The open styles, placed on the base element class with
[data-open]
- The entering styles, placed on the base element class with
[data-entering]
In newer browsers, there is a feature called @starting-style
which allows transitions to occur on open for conditionally-mounted components:
/* Base UI API - Polyfill */
.PopoverPopup[data-entering] {
opacity: 0;
transform: scale(0.9);
}
/* Official Browser API - no Firefox support as of May 2024 */
@starting-style {
.PopoverPopup[data-open] {
opacity: 0;
transform: scale(0.9);
}
}
CSS animations
CSS animations can also be used, requiring only two separate declarations:
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
}
@keyframes scale-out {
to {
opacity: 0;
transform: scale(0.9);
}
}
.PopoverPopup {
animation: scale-in 0.2s forwards;
}
.PopoverPopup[data-exiting] {
animation: scale-out 0.2s forwards;
}
JavaScript animations
The keepMounted
prop lets an external library control the mounting, for example framer-motion
's AnimatePresence
component.
function App() {
const [open, setOpen] = useState(false);
return (
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger>Trigger</Popover.Trigger>
<AnimatePresence>
{open && (
<Popover.Positioner keepMounted>
<Popover.Popup
render={
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
}
>
Popover
</Popover.Popup>
</Popover.Positioner>
)}
</AnimatePresence>
</Popover.Root>
);
}
Animation states
Four states are available as data attributes to animate the popup, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the keepMounted
prop.
[data-open]
-open
state istrue
.[data-entering]
- the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.[data-exiting]
- the popup is in the process of being removed from the DOM, but is still mounted.
Instant animation
Animations can be removed under certain conditions using the data-instant
attribute on Popover.Popup
. This attribute can be used unconditionally, but it also has different values for granular checks:
data-instant="click"
indicates the popover was opened with keyboard or virtual click.data-instant="dismiss"
indicates the popover was closed with the esc key or tabbed outside of.
In either case, you may want to remove animations:
.PopoverPopup[data-instant] {
transition-duration: 0s;
}
Overriding default components
Use the render
prop to override the rendered elements with your own components.
// Element shorthand
<Popover.Popup render={<MyPopoverPopup />} />
// Function
<Popover.Popup render={(props) => <MyPopoverPopup {...props} />} />
API Reference
PopoverRoot
The foundation for building custom-styled popovers.
Prop | Type | Default | Description |
---|---|---|---|
animated | bool | true | Whether the popover can animate, adding animation-related attributes and allowing for exit animations to play. Useful to disable in tests to remove async behavior. |
closeDelay | number | 0 | The delay in milliseconds until the popover popup is closed when openOnHover is true . |
defaultOpen | bool | false | Whether the popover popup is open by default. Use when uncontrolled. |
delay | number | 300 | The delay in milliseconds until the popover popup is opened when openOnHover is true . |
onOpenChange | func | Callback fired when the popover popup is requested to be opened or closed. Use when controlled. | |
open | bool | false | Whether the popover popup is open. Use when controlled. |
openOnHover | bool | false | Whether the popover popup opens when the trigger is hovered after the provided delay . |
PopoverTrigger
Renders a trigger element that opens the popover.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
PopoverPositioner
The popover positioner element.
Prop | Type | Default | Description |
---|---|---|---|
alignment | enum | 'center' | The alignment of the popover element to the anchor element along its cross axis. |
alignmentOffset | number | 0 | The offset of the popover element along its alignment axis. |
anchor | union | The element to which the popover element is anchored to. | |
arrowPadding | number | 5 | Determines the padding between the arrow and the popover edges. Useful when the popover element has rounded corners via border-radius . |
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
collisionBoundary | union | 'clipping-ancestors' | The boundary that the popover element should be constrained to. |
collisionPadding | union | 5 | The padding between the popover element and the edges of the collision boundary to add whitespace between them to prevent them from touching. |
container | union | The element the popover positioner element is appended to. | |
hideWhenDetached | bool | false | Whether the popover element is hidden if it appears detached from its anchor element due to the anchor element being clipped (or hidden) from view. |
keepMounted | bool | false | Whether the popover remains mounted in the DOM while closed. |
positionMethod | enum | 'absolute' | The CSS position strategy for positioning the popover element. |
render | union | A function to customize rendering of the component. | |
side | enum | 'bottom' | The side of the anchor element that the popover element should be placed at. |
sideOffset | number | 0 | The gap between the anchor element and the popover element. |
sticky | bool | false | Whether to allow the popover to remain stuck in view while the anchor element is scrolled out of view. |
PopoverPopup
Renders the popover popup element.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
finalFocus | custom | Determines an element to focus after the popover is closed. If not provided, the focus returns to the trigger. | |
initialFocus | union | Determines an element to focus when the popover is opened. It can be either a ref to the element or a function that returns such a ref. If not provided, the first focusable element is focused. | |
render | union | A function to customize rendering of the component. |
PopoverArrow
Renders an arrow that points to the center of the anchor element.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
hideWhenUncentered | bool | false | If true , the arrow is hidden when it can't point to the center of the anchor element. |
render | union | A function to customize rendering of the component. |
PopoverBackdrop
Renders a backdrop for the popover.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
container | union | false | The container element to which the backdrop is appended to. |
keepMounted | bool | false | If true , the backdrop remains mounted when the popover content is closed. |
render | union | A function to customize rendering of the component. |
PopoverTitle
Renders a title element that labels the popover.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
PopoverDescription
Renders a description element that describes the popover.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
PopoverClose
Renders a button that closes the popover when clicked.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |