Portals

Render Into a Different Part of the DOM Tree

Portals

`createPortal` lets a component render its children into a different DOM node, while still belonging to the React tree.

3 min read Level 2/5 #react#portals#createPortal
What you'll learn
  • Render a modal into `document.body`
  • Understand event bubbling through portals

A portal lets you render children into a DOM node somewhere else on the page — typically document.body — while keeping them in the same React tree.

Useful for modals, tooltips, dropdown menus, popovers — anything that should float above the layout without being constrained by an ancestor’s overflow: hidden or position.

The API

import { createPortal } from "react-dom";

function Modal({ children, onClose }) {
  return createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.body
  );
}

Wherever in the tree you render <Modal>, the actual <div class="modal-backdrop"> ends up as a direct child of <body> — escaping any restrictive parent.

React Tree vs DOM Tree

Even though the DOM is different, the React tree is unchanged:

  • Context still works — <ThemeContext.Provider> higher up still reaches into the portaled content
  • Events still bubble through the React tree — clicking inside the modal triggers onClick handlers on its React ancestors, not its DOM ancestors

That last point is non-obvious. A click inside a portaled modal bubbles to where you wrote <Modal> in your JSX — not to <body>.

A Pattern: A Dedicated Mount Node

Some apps add a dedicated <div id="portals"> next to <div id="root"> in index.html, and portal everything there:

<body>
  <div id="root"></div>
  <div id="portals"></div>
</body>
createPortal(<Modal />, document.getElementById("portals"));

Keeps portaled content separate from the main app DOM — easier to debug.

Up Next

Catching errors before they crash the whole app.

Error Boundaries →