Intermediate0 questionsFull Guide

React Portals — Complete Interview Guide

Understand React Portals — how createPortal teleports DOM output outside the component's parent while keeping it inside the React tree, why this solves stacking context problems, and how events still bubble correctly.

The Mental Model

A React Portal teleports a component's DOM output to a different node — typically document.body — while keeping it fully inside the React component tree. The component still belongs to its parent in React's world: events bubble up through React ancestors normally, Context still works, and the component's lifecycle is tied to its React parent. But the actual DOM nodes materialise somewhere else entirely. This solves the z-index and overflow stacking context problems that make modals, dropdowns, and tooltips so painful to build.

The Explanation

The Stacking Context Problem

The classic scenario: you build a modal inside a component that has overflow: hidden or a low z-index. No matter how high you set the modal's z-index, the browser clips it because it's constrained by its parent's stacking context. The modal appears behind other elements or gets cut off.

/* Parent has overflow: hidden — modal gets clipped regardless of z-index */
.card {
  overflow: hidden;
  position: relative;
}

/* This modal is trapped inside .card even with z-index: 9999 */
.modal {
  position: absolute;
  z-index: 9999;
}

The fundamental fix is to render the modal's DOM outside the constrained parent — in document.body, where it's unrestricted. That's exactly what Portals do.

createPortal — The API

import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  // Renders children into document.body — outside the current DOM subtree
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.body  // ← the target DOM node; can be any existing DOM element
  );
}

createPortal takes two arguments: the React children to render, and the target DOM node to render them into. The children are normal React — JSX, components, event handlers, the works.

Setting Up the Portal Target

For modals and overlays, document.body works fine. For more structured apps, add a dedicated container in your HTML to keep things organised:

<!-- public/index.html -->
<body>
  <div id="root"></div>
  <div id="modal-root"></div>  <!-- portal target -->
</body>
// Use it in any component
return createPortal(
  <ModalContent />,
  document.getElementById('modal-root')
);

Event Bubbling — The Counterintuitive Part

Even though the modal's DOM nodes live in document.body, events fired inside the portal bubble through the React component tree — not the DOM tree. This means:

function Parent() {
  function handleClick() {
    console.log('Parent caught the click!'); // This DOES fire even though
  }                                           // modal is outside Parent in the DOM

  return (
    <div onClick={handleClick}>
      <p>I am Parent</p>
      <Modal /> {/* portal renders in document.body, but events bubble to Parent */}
    </div>
  );
}
Portal events bubble through the React tree, not the DOM tree. This is usually what you want — a modal that's a child of a form component can still bubble submit events up to the form.

Accessibility Considerations

Portals require explicit accessibility work that normal components don't:

  • Focus trap: when a modal opens, keyboard focus must be moved inside the modal. When it closes, focus must return to the trigger element. Use a library like focus-trap-react rather than implementing this manually.
  • aria-modal="true": tells screen readers to treat the modal as a separate UI layer, hiding the background content.
  • Role: the modal container should have role="dialog" and an accessible title via aria-labelledby.
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
>
  <h2 id="modal-title">Confirm Delete</h2>
  ...
</div>

Cleanup with useEffect

If you dynamically create the portal target (rather than using a pre-existing DOM node), clean it up when the component unmounts:

function Modal({ children }) {
  const [container] = useState(() => {
    const el = document.createElement('div');
    document.body.appendChild(el);
    return el;
  });

  useEffect(() => {
    return () => document.body.removeChild(container); // cleanup on unmount
  }, [container]);

  return createPortal(children, container);
}

Common Misconceptions

⚠️

Many developers think portals break the React component tree — portals only change where the DOM nodes appear. In React's tree, a portal's children are still children of the component that rendered the portal. Context, error boundaries, and event bubbling all work through the React tree, not the DOM position.

⚠️

Many developers think events inside a portal don't bubble to ancestors — events bubble through the React component tree regardless of DOM position. A click inside a portal modal WILL bubble to the React ancestor that rendered the portal, which can cause unexpected double-handling if you don't stopPropagation.

⚠️

Many developers think portals are only for modals — any UI that needs to escape a parent's stacking context benefits from portals: tooltips, dropdowns, popovers, toasts, context menus, and date pickers all commonly use them.

⚠️

Many developers forget accessibility when using portals — rendering a modal in document.body doesn't automatically move keyboard focus or trap it. Without explicit focus management (focus-trap-react), screen reader and keyboard users can tab out of the modal into the background content.

⚠️

Many developers think portals cause memory leaks if the target is created dynamically — only if you forget the useEffect cleanup. If you use document.getElementById on a pre-existing node (like a #modal-root div in index.html), no cleanup is needed.

⚠️

Many developers think they need portals for every modal — if the parent component has no overflow:hidden, no transform, no filter, and a high enough z-index, a normal absolutely-positioned component works fine. Only reach for portals when you're actually fighting a stacking context.

Where You'll See This in Real Code

Modals and dialogs: every modal library (Radix UI Dialog, Headless UI Dialog, MUI Modal) uses portals internally to render the overlay and dialog into document.body, escaping any parent overflow:hidden or z-index constraints.

Tooltips and popovers: tooltip libraries use portals so a tooltip on a button inside a table cell isn't clipped by the table's overflow:hidden. The tooltip renders in body at the calculated screen position.

Dropdown menus: select boxes and autocomplete dropdowns render their options list via portal so it overflows any parent container and appears above other UI elements regardless of the component's position in the DOM.

Toast notifications: notification systems (React-Toastify, Sonner) render all toasts in a fixed container portalled into body, independent of where the notification was triggered in the component tree.

Drag-and-drop: drag previews (the ghost element that follows your cursor) are portalled into body so they render above everything and aren't clipped by any parent container.

Nested modals: a confirmation modal that appears on top of a main modal — both portalled into body — avoids stacking context issues between the two modal layers.

Interview Cheat Sheet

  • createPortal(children, domNode) — renders children into domNode outside the current DOM subtree
  • React tree is unchanged — context, error boundaries, and event bubbling still flow through the React parent
  • Event bubbling goes through the React tree (not the DOM tree) — clicks inside a portal bubble to React ancestors
  • Use document.body or a dedicated #modal-root div as the target
  • Dynamically created target nodes: append in useState initialiser, remove in useEffect cleanup
  • Accessibility: role='dialog', aria-modal='true', aria-labelledby, and a focus trap on open
  • Use case: any UI that needs to escape overflow:hidden, transform, or low z-index stacking contexts
  • React.createPortal is stable — no special dependencies, works in React 16+
💡

How to Answer in an Interview

  • 1.Lead with the problem, not the API: 'Portals solve the stacking context problem — when you have a modal inside a component with overflow:hidden or a low z-index, the modal gets clipped regardless of its z-index value. A portal teleports the modal's DOM nodes to document.body where it's unrestricted.'
  • 2.The event bubbling answer is the depth question: 'Even though portal DOM nodes are in document.body, events still bubble through the React component tree — not the DOM tree. A click inside a portal modal bubbles up to the React component that rendered the portal, not to body's React ancestors. This is usually the right behaviour, but you need to be aware of it to avoid double-handling.'
  • 3.Mention accessibility proactively — it shows production experience: 'Portals require explicit accessibility work: moving focus into the modal on open, trapping focus so keyboard users can't escape to the background, and using role=dialog plus aria-modal=true. I use focus-trap-react rather than implementing the trap manually.'
  • 4.Know when NOT to use them: 'I only reach for portals when I'm actually fighting a stacking context. If the parent component has no overflow:hidden or transform, a simple position:fixed with a high z-index is simpler and avoids the complexity of portals.'
  • 5.Connect to real libraries: 'Radix UI, Headless UI, MUI, and React-Toastify all use portals internally for modals, dropdowns, and toasts. Understanding portals helps you reason about why these libraries work the way they do, even if you rarely write raw createPortal yourself.'
  • 6.The React tree vs DOM tree distinction is the key conceptual point — state it clearly in one sentence: 'A portal changes where DOM nodes appear but not where the component sits in React's tree — context and event bubbling still use the React tree.'

Practice Questions

No questions tagged to this topic yet.

Related Topics

React Error Boundaries — Complete Interview Guide
Intermediate·6–10 Qs
Rendering Reconciliation
Advanced·4–8 Qs
useRef Hook — Complete React Interview Guide
Beginner·4–8 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz