Compose: Popover, PopoverTrigger, PopoverContent. Trigger uses asChild by default — wrap any element (Button, Link, custom Pressable) and it becomes the activator.
Cross-platform: web uses position: fixed with a measured trigger rect so it escapes any ancestor overflow: hidden. Native uses RN <Modal> with a transparent backdrop and tap-outside-to-close.
Non-modal: focus is NOT trapped — the user can tab back out as normal. Dismisses on outside click and Escape.
Pass open (paired with onOpenChange) for controlled mode. Pass
defaultOpen when you want the popover open on first render (e.g.
hint after a route transition). Mixing the two prefers controlled and
ignores defaultOpen.
// Uncontrolled — Popover owns its open state.<Popover> <Popover.Trigger><Button>Open</Button></Popover.Trigger> <Popover.Content>...</Popover.Content></Popover>// Controlled — you own the state, useful for closing from inside the content// after a save, or for syncing open with route state.const [open, setOpen] = useState(false);<Popover open={open} onOpenChange={setOpen}> <Popover.Trigger><Button>Open</Button></Popover.Trigger> <Popover.Content>...</Popover.Content></Popover>