Popover

Popovers are interactive floating elements that display rich content, anchored to a trigger element when a user clicks or optionally hovers over it.

Give FeedbackBundle Size

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.

index.tsx

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>
  <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.

<Popover.Positioner anchor={anchorNode}>
<Popover.Positioner anchor={anchorRef}>
<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:

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

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:

UnstyledPopoverTransition.tsx

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.

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:

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.

PropTypeDefaultDescription
animatedbooltrueWhether the popover can animate, adding animation-related attributes and allowing for exit animations to play. Useful to disable in tests to remove async behavior.
closeDelaynumber0The delay in milliseconds until the popover popup is closed when openOnHover is true.
defaultOpenboolfalseWhether the popover popup is open by default. Use when uncontrolled.
delaynumber300The delay in milliseconds until the popover popup is opened when openOnHover is true.
onOpenChangefuncCallback fired when the popover popup is requested to be opened or closed. Use when controlled.
openboolfalseWhether the popover popup is open. Use when controlled.
openOnHoverboolfalseWhether the popover popup opens when the trigger is hovered after the provided delay.

PopoverTrigger

Renders a trigger element that opens the popover.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

PopoverPositioner

The popover positioner element.

PropTypeDefaultDescription
alignmentenum'center'The alignment of the popover element to the anchor element along its cross axis.
alignmentOffsetnumber0The offset of the popover element along its alignment axis.
anchorunionThe element to which the popover element is anchored to.
arrowPaddingnumber5Determines the padding between the arrow and the popover edges. Useful when the popover element has rounded corners via border-radius.
classNameunionClass names applied to the element or a function that returns them based on the component's state.
collisionBoundaryunion'clippingAncestors'The boundary that the popover element should be constrained to.
collisionPaddingunion5The padding between the popover element and the edges of the collision boundary to add whitespace between them to prevent them from touching.
containerunionThe element the popover positioner element is appended to.
finalFocuscustomDetermines an element to focus after the popover is closed. If not provided, the focus returns to the trigger.
hideWhenDetachedboolfalseWhether the popover element is hidden if it appears detached from its anchor element due to the anchor element being clipped (or hidden) from view.
initialFocusunionDetermines 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.
keepMountedboolfalseWhether the popover remains mounted in the DOM while closed.
positionMethodenum'absolute'The CSS position strategy for positioning the popover element.
renderunionA function to customize rendering of the component.
sideenum'bottom'The side of the anchor element that the popover element should be placed at.
sideOffsetnumber0The gap between the anchor element and the popover element.
stickyboolfalseWhether to allow the popover to remain stuck in view while the anchor element is scrolled out of view.

PopoverPopup

Renders the popover popup element.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

PopoverArrow

Renders an arrow that points to the center of the anchor element.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
hideWhenUncenteredboolfalseIf true, the arrow is hidden when it can't point to the center of the anchor element.
renderunionA function to customize rendering of the component.

PopoverBackdrop

Renders a backdrop for the popover.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
containerunionfalseThe container element to which the backdrop is appended to.
keepMountedboolfalseIf true, the backdrop remains mounted when the popover content is closed.
renderunionA function to customize rendering of the component.

PopoverTitle

Renders a title element that labels the popover.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

PopoverDescription

Renders a description element that describes the popover.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

PopoverClose

Renders a button that closes the popover when clicked.

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

Contents