Back to articles
WTF

WTF Are Portals in React?

Why React can render UI outside its parent DOM element, and when that is the right tool.

In this essay 12 sections
  1. Why is this happening?
  2. Solution
  3. So what exactly does a portal do?
  4. Small example
  5. One component, two trees
  6. When should you use this?
  7. The part that blows people’s minds: events still bubble
  8. The tradeoffs
  9. Portals do not make your modal accessible
  10. The mental model that makes portals useful
  11. A scenario you will actually hit
  12. Conclusion

You create a Modal component. It works perfectly in isolation. Then you drop it inside your app, click the button to launch it, and it just goes bonkers: it may get clipped, not appear at all, be constrained by the parent layout, or have parent styles interfere with the modal styles. If you have ever encountered this before, read on.

Why is this happening?

It can happen for a bunch of reasons. The main ones are:

  • A parent might have overflow: hidden.
  • z-index issues.
  • Constraints from the parent layout.
  • Parent styles interfering with the modal styles.
Without a portal

The modal stays inside its card

The card clips anything that extends beyond its boundary.

With a portal

The modal renders at the page level

The React relationship stays put; the DOM placement changes.

A portal changes where the modal is placed in the DOM, allowing it to escape the card that would otherwise clip it.

Solution

What if we could render the modal somewhere else in the DOM but still keep the React logic in the component? Think of it like ordering room service in a hotel. The button to call for it is in your room. The food is made in the kitchen, in a completely different part of the building. The connection between you and the service still exists; the execution just happens somewhere else.

This is exactly what React Portals do. In the analogy, the button is the portal. It’s still in the room (component hierarchy), but the service (food) comes from a different location (the kitchen). Sometimes your UI logically belongs in one place, but visually it has to appear somewhere else.

So what exactly does a portal do?

A portal lets a React component render its output into a different DOM node, outside its parent hierarchy.

That one sentence hides the distinction that separates people who use portals from people who understand them:

  • The React tree, your logic, stays exactly the same.
  • The DOM tree, the actual rendered output, is what changes.

Small example

You start with two mount points in your HTML:

<div id="root"></div>
<div id="modal-root"></div>

Then render a component into the second one:

// Modal.tsx
import { createPortal } from "react-dom";

function Modal() {
  return createPortal(
    <div className="modal">I am a modal</div>,
    document.getElementById("modal-root"),
  );
}

And use it like any other component:

// App.tsx
function App() {
  return (
    <div>
      <h1>Main App</h1>
      <Modal />
    </div>
  );
}

One component, two trees

This is the part worth drawing out, because it is where the mental model clicks.

React treeOwnership, state, and events
Apprenders
Modalstill belongs to App
DOM treePhysical browser placement
<body>
#rootmain app
#modal-rootportal target
<Modal />rendered here
createPortal() keeps Modal in the React tree while placing its DOM output in #modal-root.
A portal changes DOM placement, not the React relationships that determine state, context, and event propagation.

Two trees, one component. React logic and DOM rendering have quietly gone their separate ways.

When should you use this?

Portals are not a trick you sprinkle everywhere. They earn their place in a specific family of problems, all of them: “this thing needs to escape its container.”

  • Modals, which need to overlay the entire screen and escape a parent’s clipping or stacking context.
  • Tooltips, which should not be clipped by whatever parent they happen to sit in.
  • Dropdowns and popovers, especially inside scrollable containers.
  • Toast notifications, which belong to the global UI layer, not to any one component.

The part that blows people’s minds: events still bubble

Keep in mind: the React tree !== DOM tree. This trips a lot of people. Events still bubble through the React tree, not the DOM tree.

Look at this:

function App() {
  return (
    <div onClick={() => console.log("App clicked")}>
      <Modal />
    </div>
  );
}

Even if Modal renders all the way over in document.body, clicking inside the modal logs:

App clicked

Why? Because React handles events through its own synthetic event system. The click travels up the tree React knows about, the one in your code, not the one the browser painted. Once you understand the two-tree model, this stops being magic and starts being obvious.

  1. 01 DOM location Click inside <Modal />

    The modal is physically rendered beneath #modal-root.

  2. 02 React ownership React follows Modal → App

    The portal does not change which component owns the modal.

  3. 03 Handler runs App's onClick fires

    The event bubbles through the React tree, not the DOM tree.

A click begins in the portal’s DOM output but propagates through the React component relationships.

This can also surprise you: a click inside a portal can trigger a parent handler. If that is not the behavior you want, stop propagation inside the portal or move the handler higher in the React tree.

The tradeoffs

Nothing comes free. Here’s the honest ledger:

Pros

  • Can escape layout problems caused by overflow and the wrong stacking context.
  • Enables proper floating UI.
  • Keeps your logical component hierarchy intact.
  • React state and events keep working normally.

Cons

  • The DOM structure gets harder to reason about.
  • Debugging can get confusing.
  • You have to manage extra DOM nodes.
  • It can introduce accessibility issues if you are not careful.
  • Portals with fixed positioning can break expected scroll behavior.
  • Forgetting to clean up dynamically created nodes.

Portals do not make your modal accessible

A modal must:

  • Trap focus.
  • Handle keyboard navigation.
  • Restore focus when it closes.

Portals do not give you any of this. They move where your UI renders, nothing more. The accessibility work is yours to implement, portal or not.

  1. 01
    Name the dialog

    Give it an accessible label and modal semantics.

  2. 02
    Keep focus inside

    Tab should cycle through the dialog, not the page behind it.

  3. 03
    Handle dismissal

    Support Escape and a clear close control.

  4. 04
    Restore the trigger

    Return focus to the button that opened the dialog.

A portal gives the dialog a safe place to render. It does not provide the focus and keyboard behavior that make a modal accessible.

The mental model that makes portals useful

Three insights separate people who reach for portals reflexively from people who know exactly what they are for.

  1. Portals solve DOM constraints, not state problems. They are purely a rendering escape hatch. If your problem is about data flow, a portal will not touch it.

  2. Portals pair with positioning systems. In real apps, you lean on something like Floating UI or Popper.js. The position gets computed first, and then the element is rendered through a portal. Two jobs, two tools.

Positioning systemWhere should it sit?
  • Measurethe trigger and viewport
  • Calculatex, y, and placement
  • Adjustfor flip, shift, and collision
Example: Floating UI or Popper
PortalWhere should it render?
  • Placethe overlay in #modal-root
  • Escapeclipping and a parent context
  • PreserveReact ownership and events
Example: createPortal()
ResultA correctly placed overlay that is free to render above the UI.
Positioning calculates coordinates. A portal controls DOM placement. Production floating UI often needs both.
  1. z-index is not enough. The first instinct is to always increase the z-index. Think differently. A portal can move the element out of a problematic stacking context, but you still need to manage z-index at its destination. Raising the number is treating the symptom. Escaping the stacking context is treating the cause.
Why z-index alone fails Numbers compete inside their own stacking context.
The child modal cannot out-rank its card's parent layer. A portal moves it to a sibling layer where its own z-index can be managed.
A portal can escape a problematic stacking context, but it does not remove the need to choose an appropriate layer at the destination.

A scenario you will actually hit

Picture a complex dashboard. Cards sit inside a scroll container, and each card has its own dropdown.

Without portals, those dropdowns break. They get clipped by the scroll containers. With portals, the dropdowns render correctly, and positioning is handled separately by your positioning layer. The logic stays with the card. The rendering escapes to where it can actually be seen.

A campaign analytics dashboard with an open action menu floating above several campaign cards, fully visible outside the card that triggered it.

The dropdown belongs to one campaign card, but it can render above the dashboard rather than being clipped by the card’s scroll container.

Conclusion

The key takeaway from this whole article: remember this, and you understand portals.

Portals let you teleport UI in the DOM while keeping it connected in React.

Here’s a thought exercise for you: you have a modal inside a deeply nested component. It needs access to parent state and must overlay the entire app. Why is a portal the perfect solution here? Sit with it before you reach for the code, and provide the answer in the comments.

Join the discussion

Thoughts, questions, or a different perspective?

React to this essay or continue the conversation. Comments are powered by GitHub Discussions.