Popover
.mdClick-to-open panel anchored to its trigger that scales from the trigger edge with a spring and flips above or below to stay in the viewport.
popoveroverlaydropdowndialogfloatingcollisionformanchor
Installation
$npx shadcn@latest add @geturui/popover
Usage
usage.tsx
1import { Popover } from "@/components/ui/popover";
2
3export function InviteButton() {
4 return (
5 <Popover label="Invite to project" trigger={<button>Invite</button>}>
6 {({ close }) => (
7 <form onSubmit={(e) => { e.preventDefault(); close(); }} className="grid gap-2">
8 <input type="email" placeholder="[email protected]" aria-label="Email" />
9 <button type="submit">Send invite</button>
10 </form>
11 )}
12 </Popover>
13 );
14}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
trigger* | ReactElement | — | Focusable element that toggles the panel. Receives onClick, aria-expanded and aria-controls. |
children* | ReactNode | ((api: { close: () => void }) => ReactNode) | — | Panel content; the render-function form gets a close() helper. |
open | boolean | — | Controlled open state. |
defaultOpen | boolean | false | Open on mount when uncontrolled. |
onOpenChange | (open: boolean) => void | — | Called when the panel opens or closes. |
side | "top" | "bottom" | "bottom" | Preferred side; flips when the other side has more room. |
align | "start" | "center" | "end" | "center" | Horizontal alignment to the trigger; shifted to stay 8px inside the viewport. |
sideOffset | number | 8 | Gap between trigger and panel in px. |
label | string | — | Accessible name for the dialog when it has no labelled heading. |
className | string | — | Classes for the panel surface. |
Accessibility
- Trigger gets aria-haspopup="dialog", aria-expanded and aria-controls; the panel is role="dialog".
- Focus moves to the first field when it opens. Escape closes and returns focus to the trigger; outside press or tabbing away closes without moving focus.
- With reduced motion the panel fades without scaling or blur.