Part 42: React Portals: Rendering Modals, Tooltips & Overlays
Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
1. Why Portals?
When creating modals or tooltips, CSS styling issues often arise:
- Parent containers with overflow: hidden clip the modal.
- Complex z-index stacking contexts prevent the overlay from showing above other content.
Portals solve this by mounting the modal element directly into document.body while maintaining normal React props, context, and event bubbling.
2. Implementing a Reusable Modal with createPortal
import { useEffect } from 'react';
import { createPortal } from 'react-dom';
export default function Modal({ isOpen, onClose, title, children }) {
// Close on ESC key press
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'Escape') onClose();
};
if (isOpen) window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);
if (!isOpen) return null;
return createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
{/* Click outside backdrop to close */}
<div className="fixed inset-0" onClick={onClose} />
<div className="relative bg-white dark:bg-gray-800 rounded-xl p-6 max-w-lg w-full shadow-2xl z-10">
<div className="flex justify-between items-center mb-4">
<h3 className="text-xl font-bold">{title}</h3>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">✕</button>
</div>
<div>{children}</div>
</div>
</div>,
document.body // Target container in the DOM
);
}
3. Event Bubbling Through Portals
Even though a portal renders into document.body, an event fired inside the portal still bubbles up the React component tree as if it were a normal child!
Key Takeaways
createPortal(jsx, domNode)renders elements outside the current parent DOM node.- Solves
z-indexandoverflow: hiddenbugs for modals, tooltips, and drawers. - Event bubbling behaves according to the React component tree, not the DOM tree.
In Part 43, we explore React 18 Concurrency: useTransition and useDeferredValue!

