usePopover
usePopover is the open machine behind Popover. Spread getTriggerProps on the trigger and getPopoverProps on Popover.Root, which is surface chrome only. Menu and the Combobox recipe are built on it.
An outside press and Escape call onOpenChange(false, reason) with outside or escape. The trigger toggles, so it reports trigger on both open and close. An owner can veto one reason and keep the others; in onOpenChange(open, reason), check open === false before vetoing a trigger close. Popover has a veto demo. haspopup sets the trigger's aria-haspopup. anchorRef is the CSS anchor when the trigger is not the element getTriggerProps marks; do not pass it and also spread getTriggerProps.
const [open, setOpen] = useState(false)
const [value, setValue] = useState<string | null>(null)
const { getTriggerProps, getPopoverProps } = usePopover({ open, onOpenChange: setOpen, haspopup: 'listbox' })
<>
<Button.Root tooltip={false} {...getTriggerProps()}>{value ?? 'Choose fruit'}</Button.Root>
<Popover.Root {...getPopoverProps()}>
{/* Selecting does not close the popover: the owner closes it. */}
<OptionList.Root aria-label="Fruit" value={value} onValueChange={(next) => { setValue(next); setOpen(false) }}>
<OptionList.Option value="apple">Apple</OptionList.Option>
</OptionList.Root>
</Popover.Root>
</>
When to use
When: A non-modal surface anchored to a trigger, with open and dismiss on the hook and chrome on Popover.Root.
When not: An action menu → Menu. Modal, focus-trapped work → Dialog. A mobile full-bleed picker → BottomSheet, or useSheetPresentation to choose at runtime. A text-only hover hint → Tooltip.
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| open | boolean | no | — | Controlled open state. |
| defaultOpen | boolean | no | — | Initial open state when uncontrolled. |
| onOpenChange | (open: boolean, reason: PopoverCloseReason) => void | no | — | Fires on toggle or dismiss. Leaving open unchanged is a veto. |
| anchorRef | RefObject<HTMLElement | null> | no | — | Sets CSS anchor-name on this element and excludes it from outside press. |
| initialFocusRef | RefObject<HTMLElement | null> | no | — | Focus this element when the popover opens. |
| restoreFocusRef | RefObject<HTMLElement | null> | no | — | Return focus here when the popover closes. |
| enabled | boolean | no | true | False skips toggle, dismiss, and focus moves. |
| haspopup | "dialog" | "menu" | "listbox" | "tree" | "grid" | no | "dialog" | aria-haspopup on the trigger. Use "listbox" for an OptionList in a role-less popover. |
| id | string | no | — | Popover id. Default is a generated id. |