Radix Popover portals its content to document.body by default, which works fine for top-level
UI. The default breaks down whenever a popover trigger lives inside an ancestor that:
Dialog, AlertDialog, modal DropdownMenu) — the trap yanks focus back out
of the popover the instant it opens because the portal'd content is outside the trap's DOM
subtree.DismissableLayer, used by every *Menu/Dialog) — a click
inside the popover reads as "outside the menu" and dismisses the parent immediately.Wrapping the children of the trapping ancestor in this provider, with that ancestor's element as
container, makes nested PopoverContent portal as a DOM descendant of the trap so both focus
and dismiss-layer logic accept it.
Single descendant scope: a PopoverPortalContainerProvider only affects PopoverContent mounts
rendered as React children. It does not retroactively re-portal already-mounted popovers, and it
does not affect popovers in sibling subtrees.
Initial-mount behavior: pass null for container (the initial value of a `useState<HTMLElement
| null>(null)` paired with a ref callback on the ancestor) to keep Radix's default
document.body behavior until the ancestor mounts. Once the element exists, future popover opens
portal into it. The triggering ancestor (the trap owner) must wrap, not be wrapped by, this
provider.
function ScopeMenu() {
const [dialogEl, setDialogEl] = useState<HTMLDivElement | null>(null);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent ref={setDialogEl}>
<PopoverPortalContainerProvider container={dialogEl}>
<BookChapterControl ... />
</PopoverPortalContainerProvider>
</DialogContent>
</Dialog>
);
}
// Dropdown variant: same pattern, container is the DropdownMenuContent.
const [contentEl, setContentEl] = useState<HTMLDivElement | null>(null);
<DropdownMenu>
<DropdownMenuTrigger>...</DropdownMenuTrigger>
<DropdownMenuContent ref={setContentEl}>
<PopoverPortalContainerProvider container={contentEl}>
<BookChapterControl ... />
</PopoverPortalContainerProvider>
</DropdownMenuContent>
</DropdownMenu>
Overrides the container that descendant PopoverContent components portal into. Use it to keep popovers inside a Radix
DialogContent,DropdownMenuContent, or any other ancestor that owns a focus trap or dismiss-on-outside-click layer.