Popover
Anchored panel that flips to stay on screen, with arrow and focus handling.
Installs @floating-ui/react-dom and motion.
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/popover.jsonUsage
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
<Popover>
<PopoverTrigger asChild>
<Button>Share</Button>
</PopoverTrigger>
<PopoverContent>…</PopoverContent>
</Popover>Examples
Filters
Control open to apply a draft on submit and throw it away on Escape or Cancel.
Inline confirm
Without a color, the panel takes the trigger's. Here it's red, and so is the Delete button inside.
Placement
side and align set the preferred position. It flips and shifts to stay on screen.
API reference
Popover
Holds the open state. Renders no element of its own.
| Prop | Type | Default |
|---|---|---|
open | booleanControlled open state. | None |
defaultOpen | booleanInitial state when uncontrolled. | false |
onOpenChange | (open: boolean) => voidCalled when the trigger, a close button, Escape, Tab or a press outside asks to change state. | None |
PopoverTrigger
Renders a button that toggles the popover. Also accepts every native button attribute.
| Prop | Type | Default |
|---|---|---|
asChild | booleanMerges trigger behaviour, ARIA and ref onto its single child element. The child's own handlers run first. | false |
PopoverContent
The panel, rendered in a portal with role="dialog". Other props go to the panel. It's w-72 p-4 by default; override with className.
| Prop | Type | Default |
|---|---|---|
side | "top"Preferred side. Flips when there isn't room. | "bottom" |
align | "start"Alignment along the trigger. | "center" |
sideOffset | numberGap from the trigger in pixels. | 8 |
arrow | booleanShows an arrow that tracks the trigger, lit where the light leaks in. | false |
initialFocus | "first"On open, focus the first focusable element (or one marked data-autofocus), or the panel itself. | "first" |
color | NeonColorPalette name or hex. Falls back to the trigger's --neon, then --neon-accent, then cyan. | trigger's |
PopoverClose
Renders a button that closes the popover. Also accepts every native button attribute.
| Prop | Type | Default |
|---|---|---|
asChild | booleanMerges close behaviour onto its single child element. | false |
PopoverAnchor
Optional. Positions the panel against this element instead of the trigger, e.g. a whole field.
| Prop | Type | Default |
|---|---|---|
asChild | booleanMerges onto its single child element instead of rendering a div. | false |
CSS variables
Set on the positioned wrapper, so the panel and its children can size against the space available.
| Prop | Type | Default |
|---|---|---|
--popover-trigger-width | pxWidth of the trigger (or anchor), e.g. w-[var(--popover-trigger-width)]. | None |
--popover-available-height | pxRoom left on the chosen side. Cap tall panels with max-h-[var(--popover-available-height)]. | None |
--popover-available-width | pxRoom left across. The panel's max width already uses it. | None |
Accessibility
- The panel has role="dialog" and is labelled by its trigger, which gets aria-haspopup, aria-expanded and aria-controls.
- Opening moves focus into the panel. Escape, a press outside or a PopoverClose closes it and puts focus back on the trigger.
- Tab past the last element closes the panel and continues to whatever follows the trigger; Shift+Tab from the first returns to the trigger.
- It's non-modal: the rest of the page stays interactive, and moving focus elsewhere closes it.
- Inside a Dialog or Sheet the panel renders into that <dialog>, so it stays on the top layer and clickable.
- Without a visible trigger label, pass aria-label to PopoverContent.