Components / Utilities

Portal

Portal renders its children into a different part of the DOM — by default document.body — while keeping them in the React tree where they are written. The escape hatch every overlay is built on.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A wrapper over React's createPortal. The children stay in the component tree — props, context, and state flow into them normally — but the DOM nodes are mounted somewhere else. That separation is what lets a tooltip written inside a clipped card still escape the card's overflow: hidden.

Portal is SSR-safe: with no document available, it renders null and mounts on the client once the document exists.

Install

Portal is exported from usemotif. No separate install.

example.tsx
import { Portal } from 'usemotif';

API

Portal

stable
function Portal(props: PortalProps): JSX.Element | null
childrenReactNode

The content to render at the target. Stays in the React tree where written — only the DOM placement moves.

toHTMLElement | null

The DOM node to render into. Defaults to `document.body`. Pass an element for a custom target — a shadow-DOM root, a layout-specific layer.

Accessibility

A portal moves DOM nodes, and that can break the reading order. Screen readers and the Tab sequence follow the DOM, not the React tree — content portalled to document.body is announced and tabbed to at the end of the page, far from the control that opened it.

For a modal this is usually right: focus moves into the portalled content deliberately, managed by a FocusScope. For a tooltip or a popover tied to a specific control, it can be wrong — the user tabs past the trigger and the related content is nowhere near it. When you portal interactive content, manage focus explicitly and consider aria-owns to restore the logical relationship.

Examples

A toast rendered at the body, clear of every ancestor:

example.tsx
<Portal>
<Box position="fixed" bottom="$4" right="$4">
  <Toast>{message}</Toast>
</Box>
</Portal>

Portal into a custom layer element:

example.tsx
<Portal to={document.getElementById('overlay-root')}>
<Tooltip>{label}</Tooltip>
</Portal>