Reparent
Move rendered content to a new place in the DOM without unmounting it. React state, DOM state, and iframes survive the move.
Usage
Render content once with ReparentContent, place it with a ReparentSlot.
When the slot moves (a different parent, a swapped layout, dialog to drawer),
the content's React subtree never unmounts, and where the browser supports
moveBefore
(Chrome/Edge 133+, Firefox 144+) DOM state moves with it: iframes don't
reload, focus, scroll, and animations are kept. Without moveBefore (Safari)
the node is re-appended: React state still survives, an iframe reloads.
import {
ReparentContent,
ReparentSlot,
supportsMoveBefore,
useReparent,
} from '@epilot/spark-ui/reparent'function Layout({ sidebar }: { sidebar: boolean }) {
const handle = useReparent()
return (
<>
{sidebar ? (
<aside>
<ReparentSlot handle={handle} />
</aside>
) : (
<main>
<ReparentSlot handle={handle} />
</main>
)}
<ReparentContent handle={handle}>
<VideoPlayer />
</ReparentContent>
</>
)
}If the move happens because React unmounts the current slot's whole subtree
(swapping a dialog for a drawer), call handle.park() before the state
update that triggers the swap: React tears down the old tree ancestors-first,
so waiting for an unmount cleanup is too late. See the full pattern in the
Responsive Dialog iframe example.
API Reference
| Export | Description |
|---|---|
useReparent() | Creates the persistent host. Returns { host, park } |
ReparentContent | Renders children into the host exactly once; takes handle |
ReparentSlot | A div that adopts the host on mount; takes handle plus any div props |
handle.park() | Moves the host to safety (hidden, on document.body); call before a state update that unmounts the current slot |
supportsMoveBefore | Whether this browser preserves DOM state on move; use it to lock layout swaps for iframes where it is false |