<!-- Protocore Design System — Portal -->
# Portal

- **Category:** Utilities (`utilities`)
- **Slug:** `utilities/portal`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Portal } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/utilities/portal

> Teleports its children into another DOM node (default document.body) via a client-side React portal.

## 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`.

## Props

_No documented props._

## Examples

### 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.

```tsx
import * as React from "react";
import { Portal, Button, Text } from "@protocore/pds";

export default function PortalBasics() {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ overflow: "hidden", border: "1px solid var(--pds-border-faint)", padding: 16 }}>
      <Text size="sm" color="muted" style={{ marginBottom: 12 }}>
        This box clips overflow. The banner below is portaled to the body, so it escapes the clip.
      </Text>
      <Button variant="secondary" size="sm" onClick={() => setOpen((v) => !v)}>
        {open ? "Hide" : "Show"} portaled banner
      </Button>
      {open ? (
        <Portal>
          <div
            style={{
              position: "fixed",
              insetInlineStart: 16,
              insetBlockEnd: 16,
              zIndex: 1400,
              background: "var(--pds-color-surface-3)",
              border: "1px solid var(--pds-border-strong)",
              boxShadow: "var(--pds-shadow-overlay)",
              padding: "12px 16px",
            }}
            role="status"
          >
            <Text size="sm">Portaled to document.body — not clipped by the box above.</Text>
          </div>
        </Portal>
      ) : null}
    </div>
  );
}
```

## 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

**Notes**

- 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.

## Related

`client-only`, `dialog`, `popover`, `tooltip`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
