@slithy/portal
v1.1.0
Published
A hooks-based React portal.
Readme
@slithy/portal
A hooks-based React portal. Automatically handles creating and tearing down DOM root elements — no need to ensure a target already exists.
Installation
npm install @slithy/portalPeer dependencies: react@^17 || ^18 || ^19, react-dom@^17 || ^18 || ^19
Portal
Renders children into a portal outside the current React tree. By default, portals are appended to document.body.
import { Portal } from '@slithy/portal'
<Portal>
<MyTooltip />
</Portal>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Content to render in the portal |
| id | string | "portal" | ID of the portal root element. If an element with this ID already exists in the DOM, it is reused (persistent portal). Otherwise a new element is created and appended to <body>. |
| element | string | "div" | HTML tag to use for the portal wrapper element |
| zIndex | string | — | Sets style.zIndex on the portal root element |
| attachment | (root, element) => void | root.appendChild(element) | Custom function to attach the portal element to its root |
Persistent vs. transient portals
- Transient (default
id="portal"): A new wrapper element is created per portal instance and removed on unmount. - Persistent (any other
id): The root element with that ID is reused across instances — only one DOM node is created for all portals sharing the same ID. Useful for tooltip or modal containers.
// Persistent — all tooltips share one #tooltips container
<Portal id="tooltips">
<Tooltip />
</Portal>
// Transient — each portal creates and removes its own wrapper
<Portal>
<FloatingMenu />
</Portal>Custom attachment
By default, content is appended to the portal root. Pass attachment to customize this — for example, to prepend instead:
<Portal
id="notifications"
attachment={(root, element) => root.prepend(element)}
>
<Notification />
</Portal>