Chapter 1 of 4

What a Portal Is

Rendering into a different DOM node while staying in the React tree.

createPortal renders children into a DOM node somewhere else in the document, while keeping them exactly where they are in the React tree.

import { createPortal } from "react-dom";

function Modal({ children }) {
  return createPortal(children, document.body);
}

Why you need one

A modal or tooltip nested deep inside the page inherits its ancestors' CSS. overflow: hidden clips it, a transform traps position: fixed, and a low z-index on an ancestor puts it behind other content. No amount of styling on the modal itself can escape those.

  • overflow: hidden on any ancestor clips the popup.
  • A transform or filter on an ancestor makes it the containing block for position: fixed.
  • Stacking contexts mean a child can never rise above a sibling of its ancestor.

Rendering into document.body sidesteps all three.

Events still bubble through React

This is the part that surprises people. A portal's DOM node is elsewhere, but events bubble up the React tree, not the DOM tree - so a click inside a portal reaches handlers on its React parent.

function Panel() {
  return (
    <div onClick={() => console.log("parent heard the click")}>
      <Modal>
        <button>Click me</button>
      </Modal>
    </div>
  );
}
// Clicking the button logs "parent heard the click",
// even though the button is a child of document.body

Context works too

Because the component stays in the React tree, it keeps access to every context and every error boundary above it. Portals change the DOM position and nothing else.