Stop 08 · ReactTopics · 16/20

Portals

Part of the Frontend Developer Roadmap

In short

Modals, tooltips and toasts often get clipped by a parent's overflow: hidden or buried under z-index. Portals render them somewhere else in the DOM while keeping them in your component.

`createPortal` renders a component's children into a DOM node outside its parent hierarchy — the standard way to build modals, tooltips and dropdowns that need to escape overflow/z-index constraints.

The jaadu mascot collects a cardboard parcel from the security cabin at a housing society gate.

Think of it like this

Parcel kept at the society gate

Your Amazon parcel waits at the security cabin, not outside your flat. It sits somewhere else, but it's still yours, with your name on it. A portal puts a modal's HTML at the end of body, while React still treats it as your component's child.

Watch it happen

Where the modal's HTML really lives

<body>
div#root
App
Card (clipped)
Details button
Modal backdrop

1/4In your JSX, the modal is written inside Card, and Card has overflow: hidden.

The big ideas

What you need to know

createPortal(jsx, node)

It renders the JSX inside any DOM node you choose, usually document.body.

Still a React child

Props, state and context all work as normal, because in React's tree the portal stays where you wrote it.

Events bubble the React way

A click inside the portal bubbles up to its React parents, even though the DOM elements live elsewhere.

Handle focus and Escape

Portals fix position, not accessibility. Keep focus inside modals and close them on Escape, or use the native <dialog>.

See it in code

Walk through the code

tsx
1import type { ReactNode } from "react";2import { createPortal } from "react-dom";3 4type Props = { open: boolean; onClose: () => void; children: ReactNode };5 6export function Modal({ open, onClose, children }: Props) {7  if (!open) return null;8  return createPortal(9    <div className="backdrop" onClick={onClose}>10      <div className="modal" onClick={(e) => e.stopPropagation()}>11        {children}12      </div>13    </div>,14    document.body,15  );16}

1/4When open is false, the modal renders nothing at all.

Clicking the backdrop closes the modal, and stopPropagation stops clicks inside the box from reaching it. Even inside a card with overflow: hidden, it never gets clipped.

Save yourself the pain

Common mistakes

Oops

Fighting overflow: hidden by giving a modal inside a card z-index: 99999.

Do this instead

z-index can't escape a clipped or stacked parent. Render the modal through a portal into document.body.

Oops

Touching document.body while a Next.js page renders on the server.

Do this instead

document doesn't exist on the server. Render the portal only after the component has mounted in the browser.

Your mission

A modal that can't be clipped

~25 min

Quick check

A button inside a portal modal is clicked. The modal's React parent has an onClick. Does it fire?

Go deeper

  1. 1Build a modal component using `createPortal` into a `#modal-root` div.
  2. 2Notice that events still bubble through the React tree, not the DOM tree.
  3. 3Compare a portal-based modal to one nested normally to see the z-index/overflow issues it solves.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge