Popover

A floating panel anchored to a trigger, holding interactive content, from @clerk/headless. It is a headless primitive: it supplies open state, portalling, floating-ui positioning (flip/shift/arrow), focus management, dismissal (outside press / Escape), and ARIA wiring, but ships no styles — you bring your own CSS by targeting the data-* attributes each part emits.

Example

The demo below is intentionally unstyled — it renders the raw primitive so you can see its behavior and ARIA wiring. Open it; it anchors to the trigger and dismisses on outside press or Escape.

Usage

import { Popover } from '@clerk/headless/popover';

<Popover.Root>
  <Popover.Trigger>Settings</Popover.Trigger>
  <Popover.Portal>
    <Popover.Positioner>
      <Popover.Popup>
        <Popover.Arrow />
        <Popover.Title>Preferences</Popover.Title>
        <Popover.Description>Adjust your settings below.</Popover.Description>
        <Popover.Close>Done</Popover.Close>
      </Popover.Popup>
    </Popover.Positioner>
  </Popover.Portal>
</Popover.Root>;

Controlled

const [open, setOpen] = useState(false);

<Popover.Root
  open={open}
  onOpenChange={setOpen}
>
  {/* trigger + portal */}
</Popover.Root>;

Parts

PartDefault ElementDescription
Popover.Rootnone (context)Owns open state, positioning, and the focus/dismissal wiring
Popover.Trigger<button>Toggles the popover; acts as the positioning anchor
Popover.Portalnone (portal)Portals its children; renders nothing until mounted
Popover.Positioner<div>Floating-positioned container (role="dialog", focus manager)
Popover.Popup<div>Visual content wrapper inside the positioner
Popover.Arrow<svg>Optional arrow pointing at the anchor
Popover.Title<h2>Heading; wires aria-labelledby on the positioner
Popover.Description<p>Description; wires aria-describedby on the positioner
Popover.Close<button>Closes the popover on click

All rendered parts accept a render prop for polymorphic rendering and standard HTML attributes for their default element. Popover.Arrow accepts floating-ui FloatingArrow props (width, height, tipRadius, …). Popover.Portal is optional.

Props

Popover.Root

PropTypeDefaultDescription
openbooleanControlled open state
defaultOpenbooleanfalseInitial open state (uncontrolled)
onOpenChange(open: boolean) => voidCalled when the open state changes
placementPlacement'bottom'Placement relative to the trigger (e.g. 'top', 'right-end')
sideOffsetnumber4Gap in px between the trigger and the popup
modalbooleanfalseTrap focus inside the popover

Popover.Portal

PropTypeDefaultDescription
rootHTMLElement | null | RefObject<HTMLElement>Portal mount target

Popover.Trigger, Popover.Positioner, Popover.Popup, Popover.Title, Popover.Description, and Popover.Close take no additional props beyond standard HTML attributes for their default element (Title/Description manage their own id). Alignment and collision padding are encoded in the single placement prop.

Styling

Each part emits data-* attributes you can target with any CSS solution:

AttributeApplies ToDescription
data-openTrigger, PopupPresent when the popover is open
data-closedTrigger, PopupPresent when closed (Popup stays mounted to exit)
data-sidePositioner, ArrowResolved side: top / bottom / left / right
data-starting-stylePopupPresent on the entering frame
data-ending-stylePopupPresent during the exit animation

The positioner exposes anchor/viewport geometry as CSS variables for sizing and transform-origin-based animations:

VariableDescription
--cl-anchor-widthTrigger width
--cl-anchor-heightTrigger height
--cl-available-widthAvailable width between the anchor and viewport edge
--cl-available-heightAvailable height between the anchor and viewport edge
--cl-transform-origintransform-origin pointing back toward the anchor
.cl-popover-popup {
  transform-origin: var(--cl-transform-origin);
  opacity: 1;
  transition: opacity 120ms ease, transform 120ms ease;
}
.cl-popover-popup[data-starting-style],
.cl-popover-popup[data-ending-style] {
  opacity: 0;
  transform: scale(0.95);
}