Popover
A click-triggered floating panel for arbitrary content — positioning, an optional arrow, and built-in dismiss.
- Status
- Since
0.1.0- Accessibility pattern
- APG dialog (non-modal)
Last updated
Install
Add Popover from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/popoverThe same command installs the registry items it composes: @vegastack/floating-surface.
Usage
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverClose,
PopoverTitle,
PopoverDescription,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
<Popover>
<PopoverTrigger render={<Button variant="outline">Open popover</Button>} />
<PopoverContent>
<PopoverTitle>About this layer</PopoverTitle>
<PopoverDescription>
Floats arbitrary content next to the trigger.
</PopoverDescription>
</PopoverContent>
</Popover>;Anatomy
Popover is a compound component built on Base UI's Popover. Compose the parts inside the root:
<Popover>
<PopoverTrigger render={<Button>Open</Button>} />
<PopoverContent side="bottom" align="center">
<PopoverTitle>Title</PopoverTitle>
<PopoverDescription>Supporting description text.</PopoverDescription>
{/* arbitrary content — text, a form, a menu, ... */}
<PopoverClose render={<Button variant="outline">Done</Button>} />
{/* <PopoverArrow /> — or pass `arrow` to PopoverContent */}
</PopoverContent>
</Popover>Popover— the root; owns open/close state (open/defaultOpen/onOpenChange). Modal by default in VegaStack so anchored pickers do not shift while the page scrolls; passmodal={false}for a lightweight non-blocking popover.PopoverTrigger— the control that opens the popover on click (data-slot="popover-trigger"). Passrenderto compose it with aButton.PopoverContent— the floating panel (data-slot="popover-content"). Composes Base UI's Portal + Positioner + Popup, ownsside/sideOffset/align/collisionPadding, exposesportalProps/positionerProps, can wrap children in an optional Base UI Viewport viaviewportProps, animates enter/exit, and optionally renders an arrow.PopoverTitle— the popover's accessible name (data-slot="popover-title",<h2>). Wired to the popup viaaria-labelledby.PopoverDescription— supporting text (data-slot="popover-description",<p>). Wired viaaria-describedby.PopoverClose— closes the popover (data-slot="popover-close"). Passrenderto compose it with aButton.PopoverArrow— a triangle anchored to the trigger (data-slot="popover-arrow"). Rendered automatically whenPopoverContentgetsarrow, or composed directly.
Examples
Form panel
Popovers can host arbitrary interactive content — here a small inline form. Use PopoverClose for the action buttons so they dismiss the panel.
Sides
side places the panel on the top, right, bottom, or left of the trigger (default bottom). Base UI flips the side automatically when it would collide with the viewport edge.
Alignment
align shifts the panel along the chosen side — start, center (default), or end — to line its edge up with the trigger.
Arrow
Pass arrow to PopoverContent to render a directional PopoverArrow pointing back at the trigger. You can also compose PopoverArrow directly inside the content for full control.
<PopoverContent>
<PopoverArrow />
<PopoverTitle>Direct compose</PopoverTitle>
{/* ... */}
</PopoverContent>Non-modal
By default a popover is modal — it locks background scroll so anchored pickers don't shift. Pass modal={false} for a lightweight, non-blocking popover that keeps the rest of the page scrollable and interactive.
Playground
Pick a side and toggle the arrow, open the popover, then copy the generated JSX.
<Popover>
<PopoverTrigger render={<Button variant="outline">Open popover</Button>} />
<PopoverContent>
<PopoverTitle>About this layer</PopoverTitle>
<PopoverDescription>Floats arbitrary content next to the trigger.</PopoverDescription>
</PopoverContent>
</Popover>API Reference
Popover
Popover adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
PopoverTrigger
PopoverTrigger adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on PopoverTrigger
| Attribute | Values |
|---|---|
data-slot | "popover-trigger" |
PopoverContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | "center" | Alignment relative to the chosen side. |
arrow | boolean | false | Render a directional arrow pointing at the trigger. |
collisionPadding | Padding | 8 | Minimum distance to keep between the popover and the viewport edge, in pixels. |
portalProps | Omit<Omit<PopoverPortalProps, "ref"> & React.RefAttributes<HTMLDivElement>, "children"> | — | Props forwarded to the underlying Base UI Portal. |
positionerProps | Omit<Omit<PopoverPositionerProps, "ref"> & React.RefAttributes<HTMLDivElement>, "align" | "children" | "collisionPadding" | "side" | "sideOffset"> | — | Props forwarded to the underlying Base UI Positioner. |
side | Side | "bottom" | Which side of the trigger to place the popover on. |
sideOffset | number | OffsetFunction | 8 | Distance between the trigger and the popover, in pixels. |
viewportProps | Omit<Omit<PopoverViewportProps, "ref"> & React.RefAttributes<HTMLDivElement>, "children"> | — | Props forwarded to an optional Base UI Viewport that wraps popup children. |
PopoverClose
PopoverClose adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on PopoverClose
| Attribute | Values |
|---|---|
data-slot | "popover-close" |
PopoverArrow
PopoverArrow adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
PopoverTitle
PopoverTitle adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on PopoverTitle
| Attribute | Values |
|---|---|
data-slot | "popover-title" |
PopoverDescription
PopoverDescription adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on PopoverDescription
| Attribute | Values |
|---|---|
data-slot | "popover-description" |
Accessibility
- When a
PopoverTitle/PopoverDescriptionare present, the popup is wired to them as itsaria-labelledby/aria-describedby— include a title whenever the panel has a heading. - The popover opens on trigger click. Focus moves into the panel (first tabbable element) on open and is restored to the trigger on close.
- Dismiss is built in — clicking outside the panel or pressing Esc closes it.
- The popup itself can receive keyboard focus (initial focus, focus wrap), so its native outline is deliberately not stripped — the centralized
:focus-visibleoutline is its indicator (WCAG 2.4.7, register P0-02). Composed controls — theButtontrigger, anyInputinside, and thePopoverClosebuttons — each carry their own visible focus treatment.
| Key | Action |
|---|---|
| Enter / Space | Activate the focused trigger to open the popover. |
| Tab / Shift + Tab | Move focus between controls inside the open panel. |
| Esc | Close the popover and return focus to the trigger. |
| Contract | States tested |
|---|---|
| Behaviour | default, open |
| Accessibility | described, focus-visible, labeled, semantic-html |
| Visual | default |