/// Utilities
Portal
Teleports its children into another DOM node (default document.body) via a client-side React portal.
import { Portal } from "@protocore/pds";Rendering to the body
Anything inside a Portal renders into document.body instead of its parent, escaping overflow: hidden, transform, and stacking-context traps. It is mount-guarded, so it renders nothing during SSR and the first paint, then portals on the client.
When to use it
The PDS overlays — Dialog, Popover, DropdownMenu, Tooltip, Sheet, Toast — already portal themselves through Radix, so you rarely need this directly. Reach for Portal when you're building a bespoke floating layer that isn't one of those, and a parent's overflow or transform would otherwise clip it.
Pass container to target a specific node — an Element, or a function that returns one (evaluated on the client). Without it, children land in document.body.
Usage
Do
- Use it for bespoke floating layers that escape a clipping ancestor.
- Pass a `container` to scope the portal to a specific mount node.
- Let the built-in overlays portal themselves — they already do.
Don't
- Reimplement Dialog/Popover on top of it; use the real components.
- Assume its children are in the SSR HTML; they mount on the client.
- Portal focusable content without managing focus return yourself.
Accessibility
- Portaled content leaves its DOM position, so manage focus and `aria-*` relationships (labelling, `aria-controls`) explicitly — the visual and DOM order no longer match.
- For modal semantics (focus trap, scroll lock, dismissal) use Dialog, which layers those behaviours on top of a portal.