Popover
A floating panel anchored to a trigger, opened by click—compact forms, confirmations, and contextual actions.
Use it for secondary forms, filters, or actions that should stay in context without navigating away. It uses Blue Yonder surface styling and portals through ThemeProvider so the panel renders in the correct container.
Unlike HoverCard, which reveals content on hover, Popover is intended for explicit open/close interaction and can host interactive controls such as Input, Button, or Label.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition to build a Popover:
Popover
├── PopoverTrigger
├── PopoverAnchor (optional)
└── PopoverContentPopoverContent already wraps the panel in a portal that targets the ThemeProvider container, so you typically render it directly without a separate portal. Behavior follows Radix Popover.
Usage
import {
Button,
Input,
Label,
Popover,
PopoverContent,
PopoverTrigger,
} from '@by/experience-system';Popover and its parts are client components ('use client'). Wrap your tree in ThemeProvider so PopoverContent can resolve the portal container. Use them inside a Client Component or a dynamic import when using the Next.js App Router.
<Popover>
<PopoverTrigger asChild>
<Button>Open popover</Button>
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="grid gap-4">
<div className="space-y-2">
<h4 className="text-lg leading-none font-medium">Dimensions</h4>
<p className="text-sm text-neutral-11">Set the dimensions for the layer.</p>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input id="width" defaultValue="100%" className="col-span-2 h-8" />
</div>
</div>
</PopoverContent>
</Popover>When to use
- Compact tasks next to a control (dimensions, filters, quick edits) where a full-page or
Dialogwould feel heavy. - Optional detail that should not clutter the default layout until the user asks for it.
- Touch and keyboard flows where hover is not reliable—popover uses explicit activation like
DropdownMenu, not pointer hover.
When not to use
- Blocking or critical flows that must capture full attention—prefer
DialogorDrawerwith clear primary actions. - Hover-only previews—use
HoverCardorTooltipwhen the content is read-only and hover is acceptable for your audience. - Long scrolling forms or dense multi-step content—use a dedicated page or larger overlay surface.
API Reference
Pieces map to Radix UI Popover primitives. The tables below mirror the upstream API in full; styling, motion, and the portal container come from @by/experience-system and ThemeProvider.
Popover
Popover contains all the parts of a popover. Exported as Popover; forwards Popover.Root props from radix-ui.
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | No default value |
open | boolean | No default value |
onOpenChange | function | No default value |
modal | boolean | false |
PopoverTrigger
PopoverTrigger — the control that toggles the popover. Use asChild to merge props into a Button or other focusable element.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
PopoverAnchor
PopoverAnchor — optional element to position the panel against. Use it when alignment should reference something other than the trigger (for example, anchoring to a row or input while the trigger lives elsewhere).
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
virtualRef | React.RefObject<MeasurableElement> | No default value |
PopoverContent
PopoverContent — the floating panel that opens from the trigger. This export wraps Popover.Portal with the Experience System container from useContainerElement(), so you typically render PopoverContent directly without a separate PopoverPortal. Default classes provide border, shadow, padding, and enter/exit motion.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
onOpenAutoFocus | function | No default value |
onCloseAutoFocus | function | No default value |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
onFocusOutside | function | No default value |
onInteractOutside | function | No default value |
forceMount | boolean | No default value |
side | enum | "bottom" |
sideOffset | number | 8 (experience system override of Radix 0) |
align | enum | "center" (Experience System default) |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionBoundary | Boundary | [] |
collisionPadding | number | Padding | 0 |
arrowPadding | number | 0 |
sticky | enum | "partial" |
hideWhenDetached | boolean | false |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--radix-popover-content-transform-origin | The transform-origin computed from the content and trigger positions |
--radix-popover-content-available-width | The remaining width between the trigger and the boundary edge |
--radix-popover-content-available-height | The remaining height between the trigger and the boundary edge |
--radix-popover-trigger-width | The width of the trigger |
--radix-popover-trigger-height | The height of the trigger |
@by/experience-system does not currently re-export PopoverPortal, PopoverClose, or PopoverArrow. If you need them, import them from radix-ui directly; for the full primitive reference and any upstream updates, see the Radix docs.
Accessibility
Use a real control for PopoverTrigger (for example Button or Link) so keyboard users can focus it and open the panel per Radix — accessibility. Keep focus order predictable inside PopoverContent when it contains fields or actions, and provide an explicit dismiss path (close button, primary action, or onOpenChange) when the panel is non-modal.
Keyboard interactions
| Key | Description |
|---|---|
Space / Enter | Opens or closes the popover when focus is on PopoverTrigger. |
Tab | Moves focus to the next focusable element inside PopoverContent (and out of the panel after the last one when modal={false}). |
Shift + Tab | Moves focus to the previous focusable element. |
Esc | Closes the popover and returns focus to PopoverTrigger. |
Source in the repo: packages/experience-system/src/components/Popover/Popover.tsx. Agent-oriented contracts: packages/experience-system/src/components/Popover/Popover.instructions.md.