Portals — Rendering Outside the React Root
9 min read
Escaping the DOM Hierarchy
Portals render a child tree into a different DOM node than the component's parent. Despite living elsewhere in the DOM, the portal still behaves like a normal React child — events bubble up through the React component tree, not the DOM tree.
Full modal implementation
jsx
import { createPortal } from "react-dom";
import { useEffect, useRef } from "react";
function Modal({ title, children, onClose }) {
const dialogRef = useRef(null);
useEffect(() => {
// Trap focus inside modal
dialogRef.current?.focus();
function handleKey(e) {
if (e.key === "Escape") onClose();
}
document.addEventListener("keydown", handleKey);
return () => document.removeEventListener("keydown", handleKey);
}, [onClose]);
return createPortal(
<div
role="dialog"
aria-modal="true"
aria-label={title}
ref={dialogRef}
tabIndex={-1}
className="modal-overlay"
onClick={onClose}
>
<div className="modal-box" onClick={e => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>✕</button>
<h2>{title}</h2>
{children}
</div>
</div>,
document.body
);
}Event bubbling through portals
Even though the portal DOM is a sibling of <div id="root">, a click inside the portal bubbles through the React tree — so an onClick on a parent React component will still fire. This is usually what you want, but keep it in mind for click-outside-to-close logic.
Always manage focus trapping and keyboard dismissal in modals for accessibility. The dialog HTML element (with showModal()) is increasingly supported and handles much of this natively without a portal.
Ready to test yourself?
Practice React— quiz & coding exercises