The Teleport component (also known as Portal) renders children into a DOM node that exists outside the parent component’s hierarchy, useful for modals, tooltips, and overlays.
When to Use
Render modals and dialogs outside the main app container
Create tooltips and popovers that break out of overflow containers
Implement dropdown menus with proper z-index stacking
Render content to specific DOM nodes or selectors
Avoid CSS overflow and z-index issues
Basic Usage
Render to Body
Render to Selector
Render to Element
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
function Modal ({ isOpen , children }) {
if ( ! isOpen ) return null ;
return (
< Teleport to = "body" >
< div className = "modal-backdrop" >
< div className = "modal-content" >
{ children }
</ div >
</ div >
</ Teleport >
);
}
Component API
to
string | HTMLElement | RefObject<HTMLElement> | null
required
Target destination for rendering. Can be:
CSS selector string (e.g., "body", "#modal-root", ".container")
HTMLElement instance
React ref object
Valid HTML tag name (e.g., "div", "main")
Content to render in the portal
Where to insert the portal relative to the target:
"beforebegin": Before the target element
"afterbegin": First child of target
"beforeend": Last child of target
"afterend": After the target element
Examples
Modal Dialog
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { useState } from "react" ;
function App () {
const [ isOpen , setIsOpen ] = useState ( false );
return (
< div >
< button onClick = { () => setIsOpen ( true ) } > Open Modal </ button >
{ isOpen && (
< Teleport to = "body" >
< div className = "modal-overlay" onClick = { () => setIsOpen ( false ) } >
< div className = "modal" onClick = { ( e ) => e . stopPropagation () } >
< h2 > Modal Title </ h2 >
< p > Modal content goes here </ p >
< button onClick = { () => setIsOpen ( false ) } > Close </ button >
</ div >
</ div >
</ Teleport >
) }
</ div >
);
}
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { useState } from "react" ;
function Tooltip ({ children , content }) {
const [ isVisible , setIsVisible ] = useState ( false );
const [ position , setPosition ] = useState ({ x: 0 , y: 0 });
const handleMouseEnter = ( e ) => {
const rect = e . currentTarget . getBoundingClientRect ();
setPosition ({
x: rect . left + rect . width / 2 ,
y: rect . top - 10 ,
});
setIsVisible ( true );
};
return (
<>
< span
onMouseEnter = { handleMouseEnter }
onMouseLeave = { () => setIsVisible ( false ) }
>
{ children }
</ span >
{ isVisible && (
< Teleport to = "body" >
< div
className = "tooltip"
style = { {
position: 'absolute' ,
left: position . x ,
top: position . y ,
transform: 'translate(-50%, -100%)' ,
} }
>
{ content }
</ div >
</ Teleport >
) }
</>
);
}
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { useState , useRef } from "react" ;
function Dropdown ({ trigger , children }) {
const [ isOpen , setIsOpen ] = useState ( false );
const triggerRef = useRef ( null );
const [ position , setPosition ] = useState ({ x: 0 , y: 0 });
const handleToggle = () => {
if ( ! isOpen && triggerRef . current ) {
const rect = triggerRef . current . getBoundingClientRect ();
setPosition ({
x: rect . left ,
y: rect . bottom + 4 ,
});
}
setIsOpen ( ! isOpen );
};
return (
<>
< div ref = { triggerRef } onClick = { handleToggle } >
{ trigger }
</ div >
{ isOpen && (
< Teleport to = "body" >
< div
className = "dropdown-menu"
style = { {
position: 'absolute' ,
left: position . x ,
top: position . y ,
} }
>
{ children }
</ div >
</ Teleport >
) }
</>
);
}
Notification System
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { createContext , useContext , useState } from "react" ;
const NotificationContext = createContext ();
export function NotificationProvider ({ children }) {
const [ notifications , setNotifications ] = useState ([]);
const addNotification = ( message ) => {
const id = Date . now ();
setNotifications (( prev ) => [ ... prev , { id , message }]);
setTimeout (() => removeNotification ( id ), 3000 );
};
const removeNotification = ( id ) => {
setNotifications (( prev ) => prev . filter (( n ) => n . id !== id ));
};
return (
< NotificationContext.Provider value = { { addNotification } } >
{ children }
< Teleport to = "body" >
< div className = "notification-container" >
{ notifications . map (( notification ) => (
< div key = { notification . id } className = "notification" >
{ notification . message }
< button onClick = { () => removeNotification ( notification . id ) } >
×
</ button >
</ div >
)) }
</ div >
</ Teleport >
</ NotificationContext.Provider >
);
}
export const useNotifications = () => useContext ( NotificationContext );
Insert Position Control
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
function App () {
return (
< div >
< div id = "container" >
< p > Container content </ p >
</ div >
{ /* Inserts before the container */ }
< Teleport to = "#container" insertPosition = "beforebegin" >
< div > Before container </ div >
</ Teleport >
{ /* Inserts as first child */ }
< Teleport to = "#container" insertPosition = "afterbegin" >
< div > First child of container </ div >
</ Teleport >
{ /* Inserts as last child (default) */ }
< Teleport to = "#container" insertPosition = "beforeend" >
< div > Last child of container </ div >
</ Teleport >
{ /* Inserts after the container */ }
< Teleport to = "#container" insertPosition = "afterend" >
< div > After container </ div >
</ Teleport >
</ div >
);
}
Using Ref Object
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { useRef } from "react" ;
function PopoverExample () {
const portalRef = useRef ( null );
return (
< div >
< div ref = { portalRef } className = "portal-target" />
< Teleport to = { portalRef } >
< div className = "popover" >
< h3 > Popover Content </ h3 >
< p > This is rendered in the portal target </ p >
</ div >
</ Teleport >
</ div >
);
}
Nested Portals
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { useState } from "react" ;
function NestedModals () {
const [ showFirst , setShowFirst ] = useState ( false );
const [ showSecond , setShowSecond ] = useState ( false );
return (
< div >
< button onClick = { () => setShowFirst ( true ) } > Open First Modal </ button >
{ showFirst && (
< Teleport to = "body" >
< div className = "modal" style = { { zIndex: 1000 } } >
< h2 > First Modal </ h2 >
< button onClick = { () => setShowSecond ( true ) } > Open Second Modal </ button >
< button onClick = { () => setShowFirst ( false ) } > Close </ button >
</ div >
</ Teleport >
) }
{ showSecond && (
< Teleport to = "body" >
< div className = "modal" style = { { zIndex: 1001 } } >
< h2 > Second Modal </ h2 >
< button onClick = { () => setShowSecond ( false ) } > Close </ button >
</ div >
</ Teleport >
) }
</ div >
);
}
Comparison to Native Patterns
Without Portal
function Modal ({ isOpen , children }) {
if ( ! isOpen ) return null ;
return (
< div className = "modal" >
{ children }
</ div >
);
}
// Issues:
// - Rendered in parent's DOM hierarchy
// - Subject to parent's overflow/z-index
// - CSS isolation problems
With Teleport
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
function Modal ({ isOpen , children }) {
if ( ! isOpen ) return null ;
return (
< Teleport to = "body" >
< div className = "modal" >
{ children }
</ div >
</ Teleport >
);
}
// Benefits:
// - Rendered at document body level
// - Unaffected by parent overflow/z-index
// - Proper CSS isolation
Common Use Cases
Full-Screen Overlay
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
function FullScreenLoader ({ isLoading }) {
if ( ! isLoading ) return null ;
return (
< Teleport to = "body" >
< div className = "fullscreen-overlay" >
< div className = "spinner" />
< p > Loading... </ p >
</ div >
</ Teleport >
);
}
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
import { useState } from "react" ;
function ContextMenu ({ children }) {
const [ menu , setMenu ] = useState ( null );
const handleContextMenu = ( e ) => {
e . preventDefault ();
setMenu ({ x: e . clientX , y: e . clientY });
};
return (
<>
< div onContextMenu = { handleContextMenu } >
{ children }
</ div >
{ menu && (
< Teleport to = "body" >
< div
className = "context-menu"
style = { { left: menu . x , top: menu . y } }
onClick = { () => setMenu ( null ) }
>
< button > Copy </ button >
< button > Paste </ button >
< button > Delete </ button >
</ div >
</ Teleport >
) }
</>
);
}
Side Panel
import { Teleport } from "@zayne-labs/ui-react/common/teleport" ;
function SidePanel ({ isOpen , onClose , children }) {
if ( ! isOpen ) return null ;
return (
< Teleport to = "body" >
< div className = "side-panel-overlay" onClick = { onClose } >
< div className = "side-panel" onClick = { ( e ) => e . stopPropagation () } >
< button className = "close-btn" onClick = { onClose } > × </ button >
{ children }
</ div >
</ div >
</ Teleport >
);
}
Teleport uses React’s createPortal under the hood and automatically handles client-side rendering with ClientGate to prevent SSR hydration issues.
When using insertPosition, a temporary wrapper div is created and then replaced with its children after rendering, maintaining a clean DOM structure.
Make sure the target element exists in the DOM before rendering the Teleport. Use refs or ensure DOM elements are mounted first.