Popover
The Mosaic Popover — the styled Mosaic component composed from the @clerk/headless popover
primitive and themed with StyleX. It owns only what it means to float: trigger wiring, ARIA, focus
management, positioning, stacking, viewport clamps, and the enter/exit transition. It paints no
surface of its own — background, border, radius, shadow and padding come from whatever you render
inside it, usually a Card. Keeping the two apart means only one element ever draws the border.
Example
Usage
Compose the parts: Popover.Root owns the open state and placement, Popover.Trigger is the
anchor, and Popover.Popup is the floating box. Put the surface inside the popup — everything you
see comes from the Card.
import { Button } from '@clerk/ui/mosaic/components/button';
import { Card } from '@clerk/ui/mosaic/components/card';
import { Popover } from '@clerk/ui/mosaic/components/popover';
<Popover.Root>
<Popover.Trigger render={props => <Button {...props}>Open popover</Button>} />
<Popover.Popup aria-label='Account'>
<Card.Root>
<Card.Content>Flexible inner content.</Card.Content>
<Card.Footer>
<Button
color='negative'
fullWidth
>
Sign out of all accounts
</Button>
</Card.Footer>
</Card.Root>
</Popover.Popup>
</Popover.Root>;Popover.Trigger renders a <button> by default. Its render prop swaps in your own element and
receives the interaction props (ARIA attributes, click handler, and the data-open / data-closed
state attributes) from the headless layer to spread onto it.
Popover.Popup portals itself out to the document body and positions against the trigger, so it
needs no wrapper of its own.
Accessible name
The popup is a role="dialog", so it needs a name. Either pass aria-label to Popover.Popup, or
render a Popover.Title inside it — the title wires aria-labelledby for you. A popover with
neither logs a development warning.
<Popover.Root>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Popup aria-label='Account'>
<Card.Root>
<Card.Content>Flexible inner content.</Card.Content>
</Card.Root>
</Popover.Popup>
</Popover.Root>;Controlled
const [open, setOpen] = useState(false);
<Popover.Root
open={open}
onOpenChange={setOpen}
>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Popup aria-label='Account'>
<Card.Root>
<Card.Content>Flexible inner content.</Card.Content>
</Card.Root>
</Popover.Popup>
</Popover.Root>;Size
size on Popover.Popup sets the width of the floating box: sm (18rem), md (23.5rem, the
default), or lg (26rem). Each clamps to calc(100vw - 2rem) on narrow viewports. The popup also
caps its height at min(80dvh, 36rem) — for taller content, give the surface inside a scrolling
region.
<Popover.Root>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Popup size='lg'>
<Card.Root>
<Card.Content>A wider panel.</Card.Content>
</Card.Root>
</Popover.Popup>
</Popover.Root>;Placement
placement on Popover.Root sets the preferred side: top, right, bottom (the default), or
left. sideOffset sets the gap from the trigger.
Placement is a preference, not a guarantee — the popup flips to the opposite side when it would
overflow, and shifts along the cross axis to stay in view. data-side on the positioner always
reports the side actually used.
Alignment
Append -start or -end to a placement to align the popup with that edge of the trigger instead of
centering on it.
<Popover.Root
placement='bottom-end'
sideOffset={8}
>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Popup aria-label='Account'>
<Card.Root>
<Card.Content>Aligned to the trigger's end edge.</Card.Content>
</Card.Root>
</Popover.Popup>
</Popover.Root>;Cross-axis flipping is only enabled for aligned placements, so bottom-start may become
bottom-end near a viewport edge while a plain bottom will not.
Parts
| Part | Slot | Description |
|---|---|---|
Popover.Root | — | State provider; owns open/close, placement, sideOffset, modal. |
Popover.Trigger | — | Anchor element; renders a <button> and accepts a render prop. |
Popover.Popup | popover-popup | The floating box; owns size, viewport clamps, and the enter/exit run. |
Popover.Close | — | Dismisses the popover; accepts a render prop. |
Popover.Title | — | Heading; wired to the popup's aria-labelledby. |
Popover.Description | — | Description; wired to the popup's aria-describedby. |
Popover.Popup renders the portal and the floating positioner internally. The positioner carries
the popover-positioner slot and is the role="dialog" element, which is why the popup's
aria-label / aria-labelledby land there.
Popover.Title and Popover.Description are unstyled passthroughs from the headless layer — render
them through your own typography (Heading, Text) inside the surface.
Styling
Unlike the slot-recipe components, the Mosaic popover is themed with StyleX. Each styled part
carries a stable .cl-<slot> class (the slots in the table above) alongside the StyleX atoms.
Consumers never target the hashed atomic classes — override by targeting the .cl-* slot from a
CSS layer that wins over @clerk/ui/styles.css:
@import '@clerk/ui/styles.css' layer(components);
@layer overrides {
.cl-popover-popup[data-size='lg'] {
width: 30rem;
}
}The popup is intentionally transparent. To restyle the panel's background, border, radius or
shadow, style the surface you render inside it (e.g. .cl-card-root) rather than the popup.
State attributes from the headless layer are available for CSS targeting:
| Attribute | Applies To | Description |
|---|---|---|
data-open | Trigger, Popup | Present when the popover is open |
data-closed | Trigger, Popup | Present when closed (during exit) |
data-starting-style | Popup | Present on the entering frame |
data-ending-style | Popup | Present during the exit animation |
data-side | Positioner | Resolved side (top / bottom / left / right) |
data-size | Popup | Resolved size (sm / md / lg) |
The popup's enter/exit transition (opacity + scale) is driven off data-starting-style /
data-ending-style, runs for --cl-duration-base, and eases per property: linear for the fade,
--cl-ease-default for the scale. Under prefers-reduced-motion: reduce only transform drops out
of the transition — the fade still runs, since the vestibular concern is the movement.
data-open on the trigger is what a trigger component styles to hold a pressed/active look while
the popover is open.