How to Use a React Portal: Complete Guide with Examples

React portals let you render children into a DOM node that exists outside the parent component's DOM hierarchy using ReactDOM.createPortal(children, container), enabling patterns like modals and tooltips while preserving React context and event bubbling.

A react portal is a first-class feature in the facebook/react repository that solves layout constraints such as overflow: hidden or z-index stacking contexts. By mounting a subtree to a different physical DOM location, you maintain full React functionality—including state, context, and event delegation—without changing the component hierarchy.

What Is a React Portal?

A react portal is a special React element that instructs the reconciler to mount its children into a DOM container that is not a descendant of the parent component’s DOM node. Internally, calling ReactDOM.createPortal returns a ReactPortal object tagged with the HostPortal work tag, as defined in packages/react-reconciler/src/ReactPortal.js.

The portal system is deeply integrated with React’s fiber architecture. When the reconciler encounters a HostPortal tag during the render phase, it treats the portal’s children as a separate subtree while maintaining their position in the React component tree. This ensures that context providers and event listeners behave exactly as if the elements were rendered inline.

React Portal Syntax and API

The public API for creating a react portal is exposed through ReactDOM.createPortal:

ReactDOM.createPortal(children, container, key?)
  • children: Any valid React node (elements, strings, numbers, fragments, etc.) that should be rendered inside the portal.
  • container: A DOM element (instance of Element, DocumentFragment, or Document) where the children will be mounted.
  • key (optional): A unique string or number used by React to identify the portal during reconciliation, useful when rendering multiple portals.

The DOM-specific implementation resides in packages/react-dom/src/shared/ReactDOM.js, which forwards the call to the reconciler’s createPortal function in packages/react-reconciler/src/ReactPortal.js.

Practical React Portal Examples

Rendering a Modal Outside the DOM Hierarchy

Modals typically need to overlay the entire page and escape overflow: hidden constraints on parent containers. A react portal mounts the modal directly to document.body:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

function Modal({ children, onClose }) {
  // Rendered into document.body via a react portal
  return ReactDOM.createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.body
  );
}

export default function App() {
  const [open, setOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setOpen(true)}>Open modal</button>
      {open && (
        <Modal onClose={() => setOpen(false)}>
          <h2>Portal Modal</h2>
          <p>This content is rendered outside the #root element.</p>
        </Modal>
      )}
    </div>
  );
}

Even though the DOM nodes live inside document.body, the Modal component participates fully in the React tree: it receives props, can use context, and its events bubble according to the React component hierarchy, not the DOM hierarchy.

Building a Tooltip with Dynamic Positioning

Tooltips often require precise positioning relative to a trigger element while avoiding clipping by parent containers with overflow: hidden:

import React, { useRef, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

function Tooltip({ targetRef, children }) {
  const tooltipRoot = document.getElementById('tooltip-root');
  const [pos, setPos] = useState({ top: 0, left: 0 });

  useEffect(() => {
    if (targetRef.current && tooltipRoot) {
      const rect = targetRef.current.getBoundingClientRect();
      setPos({ top: rect.bottom + 4, left: rect.left });
    }
  }, [targetRef, tooltipRoot]);

  if (!tooltipRoot) return null;

  return ReactDOM.createPortal(
    <div
      className="tooltip"
      style={{ position: 'absolute', top: pos.top, left: pos.left }}
    >
      {children}
    </div>,
    tooltipRoot
  );
}

export default function Example() {
  const buttonRef = useRef(null);
  const [show, setShow] = useState(false);

  return (
    <div>
      <button 
        ref={buttonRef} 
        onMouseEnter={() => setShow(true)} 
        onMouseLeave={() => setShow(false)}
      >
        Hover me
      </button>
      {show && <Tooltip targetRef={buttonRef}>I'm a tooltip</Tooltip>}
    </div>
  );
}

This react portal approach ensures the tooltip renders into a dedicated DOM node (e.g., #tooltip-root) while maintaining React’s event system and lifecycle management.

Portaling a List into a Scrollable Sidebar

For widgets or plugin architectures, you may need to inject content into a specific region of the page defined outside your component tree:

import ReactDOM from 'react-dom';

function Sidebar({ children }) {
  const sidebarRoot = document.getElementById('sidebar-root');
  
  if (!sidebarRoot) return null;
  
  return ReactDOM.createPortal(
    <nav className="sidebar">{children}</nav>,
    sidebarRoot
  );
}

// Usage
function App() {
  return (
    <div className="main-content">
      <Sidebar>
        <ul>
          <li>Item 1</li>
          <li>Item 2</li>
        </ul>
      </Sidebar>
      <p>Main page content here</p>
    </div>
  );
}

How React Portals Work Under the Hood

The implementation of react portals spans several critical files in the facebook/react repository. Understanding these internals clarifies why portals behave like normal React components despite living in different DOM locations.

Portal Creation and Typing

In packages/react-reconciler/src/ReactPortal.js, the createPortal function constructs a ReactPortal object:

export function createPortal(
  children,
  containerInfo,
  implementation,
  key
) {
  return {
    $$typeof: REACT_PORTAL_TYPE,
    key: key == null ? null : '' + key,
    children,
    containerInfo,
    implementation,
  };
}

This object is tagged with REACT_PORTAL_TYPE and the HostPortal work tag, signaling to the reconciler that this fiber represents a portal rather than a standard DOM element or component.

Reconciler Integration

The reconciler handles portals in packages/react-reconciler/src/ReactFiberBeginWork.js and packages/react-reconciler/src/ReactFiberCommitWork.js. When encountering a HostPortal, the reconciler:

  1. Mounts the portal’s children into the specified containerInfo (the DOM node)
  2. Maintains the fiber tree structure so that the portal’s children retain their parentage in the React component tree
  3. Propagates context and events through the React tree, not the DOM tree

This architecture ensures that a react portal behaves transparently to the application code—context providers above the portal still affect components inside it, and events bubble to React event handlers in the component hierarchy regardless of where the DOM nodes are attached.

Public API Surface

The public-facing API in packages/react-dom/src/shared/ReactDOM.js provides the createPortal method that most developers use:

function createPortal(children, container, key) {
  return ReactPortal.createPortal(children, container, null, key);
}

Additionally, packages/react-is/src/index.js exports isPortal for library authors who need to detect portal objects at runtime.

When to Use React Portals

React portals solve specific UI architecture problems that standard React rendering cannot address:

  • Modals and Dialogs: Escape overflow: hidden or z-index stacking contexts imposed by parent containers. Portaling to document.body ensures overlays render above all other content.
  • Tooltips and Popovers: Position floating elements relative to trigger buttons without being clipped by scrollable ancestors or constrained layouts.
  • Widgets and Plugin Systems: Inject React components into DOM nodes managed by external scripts or legacy applications, such as sidebars, headers, or third-party containers.
  • Fixed Positioning Edge Cases: When position: fixed behaves unpredictably due to transformed ancestors, portaling to a stable container outside the transformed tree resolves positioning issues.

Summary

  • A react portal renders children into a DOM node that exists outside the parent component’s DOM tree using ReactDOM.createPortal(children, container).
  • Portals are implemented in packages/react-reconciler/src/ReactPortal.js as ReactPortal objects tagged with HostPortal, processed by the fiber reconciler to maintain component hierarchy while mounting to external DOM nodes.
  • Event bubbling and context propagation work through the React component tree, not the DOM tree, ensuring portals behave like standard components.
  • Common use cases include modals, tooltips, and widgets that must break out of CSS containment or overflow constraints.

Frequently Asked Questions

Do events bubble up through a react portal?

Yes. Despite the DOM nodes being physically separated from the parent component’s DOM element, React maintains the original component tree structure. Events fired inside a portal bubble up to parent React components according to the React component hierarchy, not the DOM hierarchy. This is handled by the reconciler in packages/react-reconciler/src/ReactFiberCommitWork.js and the event system.

Can I use multiple react portals in the same component?

Yes. You can call ReactDOM.createPortal multiple times within a single component’s render method, targeting the same container or different containers. Each portal maintains its own fiber subtree and can have independent keys. This is common when rendering multiple modals, tooltips, or widget injections from a single parent component.

Do react portals work with server-side rendering (SSR)?

React portals require a DOM container to mount into, which does not exist during server-side rendering. When using SSR, you should conditionally create portals only when document is available (i.e., in useEffect or after hydration). Attempting to call ReactDOM.createPortal during the server render will throw an error because the container node cannot be found.

How do I check if a value is a react portal?

You can use the ReactIs utility from the react-is package. Specifically, ReactIs.isPortal(value) returns true if the value is a portal object. This is useful for library authors who need to introspect React element types. The implementation resides in packages/react-is/src/index.js and checks for the $$typeof: REACT_PORTAL_TYPE property that is assigned in packages/react-reconciler/src/ReactPortal.js.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →