Portal
<Portal> relocates its children to another DOM node — by default document.body, or a selector / element you pass as to. Use it when an ancestor's overflow, z-index, or transform would clip or bury UI that should float above the page: toasts, tooltips, menus, and custom overlays.
import { Portal } from "@opentf/web"; {open && ( <Portal to="body"> <div class="toast">Saved!</div> </Portal> )}
On connect the runtime moves the portal's light-DOM children to the target; on unmount it removes them so custom-element disconnect and effect cleanups run. No manual DOM bookkeeping.
The box is overflow: hidden. An inline overlay would be clipped —<Portal to="body"> relocates the modal to <body>.
Ancestor clips anything that stays inside.
For modals, <dialog> uses the browser top layer — no portal needed. Backdrop and Esc-to-close are built in.
Open the portaled modal inside the clipped box — it renders on document.body, not inside the clip region. Compare with the native <dialog> below the demo.
The to target
to value | Resolves to |
|---|---|
| omitted | document.body |
"body" / "#root" / any selector | document.querySelector(…), falling back to body |
a DOM Element | that element directly |
<Portal to="#toast-root"> <p>Rendered into the element with id="toast-root".</p> </Portal>
Portals vs native <dialog>
For modals and dialogs, the browser top layer is often simpler — no relocation, free backdrop, Esc-to-close:
const dialogRef = $ref(); <button onclick={() => dialogRef.showModal()}>Open</button> <dialog ref={dialogRef}> <p>Native top layer</p> <button onclick={() => dialogRef.close()}>Close</button> </dialog>
Reach for <Portal> when you need a general escape hatch — stacked toasts, custom overlays, tooltips anchored to arbitrary targets, or markup that does not map cleanly to <dialog>.
On first paint the portal defers relocation until hydration finishes so slotted content can adopt server-rendered DOM in place. On a client-only build the move runs immediately. See Hydration.
Runtime API
| Export | Description |
|---|---|
Portal | Built-in custom element — use as <Portal to={…}>…</Portal> in JSX. |
Also listed on the Runtime API reference.
Related
Context — portaled subtrees still resolve
$contextfrom the logical tree.Error boundaries — throws inside portaled content route to the nearest boundary.
Components — where portals fit in the tree.
Imperative API —
$reffor native<dialog>.