React Portals Explained — Render Components Outside the DOM Tree
In this tutorial, you will learn about React Portals Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
React portals let you render a component's children into a different DOM node that exists outside the parent component's DOM hierarchy, solving z-index and overflow issues.
What You'll Learn
- What portals are and when to use them
- How to create a portal with createPortal
- How to build modals, tooltips, and dropdowns
- How event bubbling works through portals
- Accessibility considerations for portals
Why It Matters
Modals, tooltips, and dropdowns often break when clipped by parent overflow or hidden by low z-index. Portals render them at the document body level, fixing these issues while preserving the React component tree.
Real-World Use
Durga Antivirus Pro uses portals for the threat alert modal, context menus on scan results, tooltips on dashboard icons, and a full-screen notification overlay.
flowchart TD
A[React Tree] --> B[App]
B --> C[Page]
C --> D[Modal Trigger]
D -.->|createPortal| E[Modal in body]
B --> F[Tooltip]
F -.->|createPortal| G[Tooltip in body]
style E fill:#3b82f6,color:#fff
Creating a Portal
Use ReactDOM.createPortal to render outside the parent:
import { useState, useEffect } from "react";
import { createPortal } from "react-dom";
function Modal({ isOpen, onClose, title, children }) {
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
}
return () => {
document.body.style.overflow = "";
};
}, [isOpen]);
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay"
onClick={onClose}
style={{
position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)",
display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000
}}>
<div className="modal-content"
onClick={e => e.stopPropagation()}
style={{
background: "white", padding: 24, borderRadius: 12, minWidth: 400,
maxWidth: 600, boxShadow: "0 20px 60px rgba(0,0,0,0.3)"
}}>
<h2>{title}</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>,
document.body
);
}
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div style={{ position: "relative", overflow: "hidden" }}>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="My Portal Modal">
<p>This renders directly in document.body, not clipped by overflow:hidden!</p>
</Modal>
</div>
);
}
Expected output: The modal renders in document.body and is not clipped by the parent div's overflow:hidden.
Event bubbling still works through portals. A click on the modal content (which calls stopPropagation) does not close the modal, but clicking the overlay (which does not stop propagation) closes it.
Common Mistakes
- Not cleaning up portal DOM nodes
- Forgetting stopPropagation on modal content
- Not managing focus within portals
- Portals without accessible labels
- Rendering portal target before the DOM node exists
Practice Questions
- What problem do portals solve?
- How does createPortal work?
- Do events bubble through portals?
- How do you manage focus in a portal?
- When should you use a portal vs absolute positioning?
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro