useTeleport() lets you render virtual DOM content into any DOM node outside the current component's shadow root. This is essential for UI that must visually escape component boundaries — modals, tooltips, popovers, and notification toasts.
function useTeleport(target: string | Element): TeleportHandle;| Parameter | Type | Description |
|---|---|---|
target |
string | Element |
A CSS selector string or a direct Element reference |
interface TeleportHandle {
portal(content: VNode | VNode[] | null | undefined): void;
destroy(): void;
}portal(content)— Renders (or updates) the given virtual DOM tree at the target. Passnullorundefinedto clear previously rendered content.destroy()— Removes the teleport container and cleans up all rendered content. Call this inuseOnDisconnectedto prevent memory leaks.
import {
component,
html,
ref,
useOnDisconnected,
useTeleport,
} from '@jasonshimmy/custom-elements-runtime';
component('modal-trigger', () => {
const isOpen = ref(false);
const { portal, destroy } = useTeleport('#modal-root');
useOnDisconnected(destroy);
if (isOpen.value) {
portal(html`
<div class="modal-overlay">
<div class="modal">
<h2>Hello!</h2>
<button @click="${() => (isOpen.value = false)}">Close</button>
</div>
</div>
`);
} else {
portal(null); // Clear the portal when closed
}
return html`
<button @click="${() => (isOpen.value = true)}">Open Modal</button>
`;
});component('info-tooltip', () => {
const visible = ref(false);
const { portal, destroy } = useTeleport('body');
useOnDisconnected(destroy);
portal(
visible.value
? html`<div class="tooltip" role="tooltip">This is a tooltip</div>`
: null,
);
return html`
<span
@mouseenter="${() => (visible.value = true)}"
@mouseleave="${() => (visible.value = false)}"
>ℹ️</span
>
`;
});useTeleport(target)finds the target element and appends a<cer-teleport>container to it.- Each call to
portal()runs the virtual DOM renderer against that container — diffing and patching efficiently. destroy()renders an empty node set (cleaning up event listeners and refs) then removes the container from the DOM.
useTeleport() is a no-op in server-side rendering environments (where document is undefined). It returns a stub handle with empty portal() and destroy() functions.
If the target selector does not match any element, useTeleport() logs a warning and returns a no-op handle:
[useTeleport] Target "#modal-root" not found in the document.
Teleported content will not be rendered.
Ensure the target element is present in the DOM before calling useTeleport().
Content rendered to a teleport target appears like this:
<div id="modal-root">
<cer-teleport data-cer-teleport>
<!-- Your portal content renders here -->
<div class="modal-overlay">…</div>
</cer-teleport>
</div>- Always call
useOnDisconnected(destroy)to avoid orphaned DOM nodes. - You can call
portal(null)to clear content without destroying the container (avoids re-creating the container on next open). - Multiple separate
useTeleport()calls to the same target each create their own<cer-teleport>container — they do not interfere with each other. - The
<cer-teleport>container is an ordinaryHTMLElement. You can style it with CSSdisplay: contentsif you want it to be invisible to layout.