Popover
Non-modal panel anchored to a Button for filters, pickers and quick edits. Focus moves in, Esc returns it, outside clicks close; Selects and menus opened inside don't count as outside.
import { Button } from "@/components/ui/button";import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover";export function ClipFilters() { return ( <Popover> <PopoverTrigger variant="outline">Filters</PopoverTrigger> <PopoverContent align="start" className="grid gap-3"> <p className="font-medium text-sm">Filter clips</p> <Button size="sm">Apply</Button> </PopoverContent> </Popover> );}<Popover>
Non-modal panel anchored to a button: filters, pickers, quick edits. Controlled or uncontrolled.
children*ReactNodePopoverTriggerandPopoverContent.defaultOpendefaultfalsebooleanOpen at first render (uncontrolled).
onOpenChange(open: boolean) => voidCalled on the trigger, Esc or an outside click.
openbooleanControlled state; pair with
onOpenChange.
| Prop | Type | Default |
|---|---|---|
children*
| ReactNode | — |
defaultOpenOpen at first render (uncontrolled). | boolean | false |
onOpenChangeCalled on the trigger, Esc or an outside click. | (open: boolean) => void | — |
openControlled state; pair with | boolean | — |
<PopoverTrigger>
childrenReactNodeLabel; its width is reserved so loading and success never resize the button.
confirmLabelReactNodeTwo-step action: first click swaps the label to this, second click runs
onClick.confirmTimeoutnumberMs before an armed confirm step resets. Also resets on blur or Esc.
loadingbooleanDisables the button and shows a spinner before the label.
loadingLabelReactNodeLabel shown while loading, with the spinner. Defaults to
children.size"default" | "sm" | "lg" | "icon"Height and padding; "icon" is square, so give it an
aria-label.statusButtonStatusIn-place feedback:
loadingspins,successswaps the label tosuccessLabel.successLabelReactNodeText shown on
status="success", with a check icon. Defaults to "Done".swapSwapDirectionWhere the old label slides out to on a swap. Default "left".
variant"default" | "secondary" | "outline" | "ghost" | "destructive"Fill and emphasis: one primary action per view,
destructivefor irreversible ones.
| Prop | Type | Default |
|---|---|---|
childrenLabel; its width is reserved so loading and success never resize the button. | ReactNode | — |
confirmLabelTwo-step action: first click swaps the label to this, second click runs | ReactNode | — |
confirmTimeoutMs before an armed confirm step resets. Also resets on blur or Esc. | number | — |
loadingDisables the button and shows a spinner before the label. | boolean | — |
loadingLabelLabel shown while loading, with the spinner. Defaults to | ReactNode | — |
sizeHeight and padding; "icon" is square, so give it an | "default" | "sm" | "lg" | "icon" | — |
statusIn-place feedback: | ButtonStatus | — |
successLabelText shown on | ReactNode | — |
swapWhere the old label slides out to on a swap. Default "left". | SwapDirection | — |
variantFill and emphasis: one primary action per view, | "default" | "secondary" | "outline" | "ghost" | "destructive" | — |
Also accepts every Button prop.
<PopoverContent>
Focus moves in on open. Esc or an outside click closes it; Esc returns focus to the trigger. Not a focus trap: Tab past the end closes it and moves on, like any non-modal popup.
children*ReactNodePanel content.
aligndefault"center""start" | "center" | "end"Alignment with the trigger.
sidedefault"bottom""top" | "bottom"Preferred side; flips when there is no room.
| Prop | Type | Default |
|---|---|---|
children*Panel content. | ReactNode | — |
alignAlignment with the trigger. | "start" | "center" | "end" | "center" |
sidePreferred side; flips when there is no room. | "top" | "bottom" | "bottom" |
* required. Every component also takes className.