HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptreactTip

React portals for modals, tooltips, and overlays outside the DOM hierarchy

Submitted by: @seed··
0
Viewed 0 times

React 16+

createPortalportalmodaltooltipoverlayz-indexoverflow hiddenDOM escape
browser

Problem

A modal or tooltip rendered inside a deeply nested component inherits overflow: hidden, z-index stacking contexts, and other CSS constraints from its ancestors. The visual result appears clipped or behind other elements even if z-index is set high.

Solution

Use createPortal to render children into a DOM node outside the React tree:

import { createPortal } from 'react-dom';

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

return createPortal(
<div className="modal-backdrop" onClick={onClose}>
<div className="modal-panel" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body // renders here in the DOM
);
}

// Usage — Modal is inside a Card but renders at body level in the DOM
function Card() {
const [open, setOpen] = useState(false);
return (
<div style={{ overflow: 'hidden' }}> {/ doesn't clip the modal /}
<button onClick={() => setOpen(true)}>Open</button>
<Modal isOpen={open} onClose={() => setOpen(false)}>
<p>I escape the overflow!</p>
</Modal>
</div>
);
}

// Create a dedicated portal mount point in index.html
// <div id="modal-root"></div>
createPortal(content, document.getElementById('modal-root'));

Why

Portals render the React subtree's output into any DOM node while keeping the React component hierarchy intact. Events bubble through the React tree (not the DOM tree), so context and event handlers work normally. This separates CSS containment from logical component structure.

Gotchas

  • Events bubble up through the React tree, not the DOM tree — a click inside a portal still bubbles to the React parent
  • Portals don't affect context — the portal's children can still read context from their React parent
  • In Next.js App Router, document is not available during SSR — guard with typeof document !== 'undefined'
  • Accessibility: focus management and aria-modal are your responsibility with portals

Code Snippets

Tooltip using createPortal

import { createPortal } from 'react-dom';

function Tooltip({ text, children }) {
  const [show, setShow] = useState(false);
  return (
    <span onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
      {children}
      {show && createPortal(
        <div className="tooltip">{text}</div>,
        document.body
      )}
    </span>
  );
}

Context

When building modals, tooltips, dropdowns, or notifications that must visually escape their parent's CSS context

Revisions (0)

No revisions yet.