React Portals
React Portals are a powerful feature that allow you to render components outside the main DOM hierarchy of your React app. This is especially useful for creating modals, tooltips, dropdowns, or overlays, which need to appear above other content without breaking the layout.
Using portals ensures your UI remains clean, manageable, and accessible, while still keeping React’s state and event system intact.
What are React Portals?
In a normal React application, all components render inside a single root DOM element:
<div id="root"></div>
Sometimes, certain components need to render outside this root to avoid layout conflicts, z-index issues, or overflow restrictions. Examples include:
- Modal dialogs
- Tooltips and popups
- Floating dropdown menus
- Notification messages
React provides the ReactDOM.createPortal() method for this purpose.
Syntax of a Portal
ReactDOM.createPortal(child, container)
- child – The React element, string, or fragment to render.
- container – The DOM node where the element should be mounted.
This allows React components to maintain their parent-child relationships and state, even if rendered elsewhere in the DOM.
Example: Basic Portal
HTML:
<div id="root"></div>
<div id="modal-root"></div>
React Component:
import React from "react";
import ReactDOM from "react-dom";
function Modal({ children }) {
return ReactDOM.createPortal(
<div className="modal">{children}</div>,
document.getElementById("modal-root")
);
}
function App() {
return (
<div>
<h1>Welcome to React Portals</h1>
<Modal>
<h2>This content is rendered outside the main DOM root!</h2>
</Modal>
</div>
);
}
export default App;
- The
<Modal>content is rendered inside themodal-rootdiv, separate from the main app root. - React state and events continue to work normally, even though the DOM location is different.
- This approach is perfect for UI elements that need overlay positioning.
Creating a Modal with React Portals
Portals are particularly useful for modals because they often need to overlay the entire screen. Here’s a more practical example:
import { createRoot } from 'react-dom/client';
import { useState } from 'react';
import { createPortal } from 'react-dom';
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return createPortal(
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<div style={{
background: 'white',
padding: '20px',
borderRadius: '8px'
}}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>,
document.body
);
}
function MyApp() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<h1>My App</h1>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
<h2>Modal Content</h2>
<p>This content is rendered outside the App component!</p>
</Modal>
</div>
);
}
createRoot(document.getElementById('root')).render(<MyApp />);
createPortalrenders the modal content intodocument.body, outside the main App hierarchy.- The modal can overlay all page content without affecting layout.
- The modal’s open/close state is still managed via React’s
useStatehook.
Event Bubbling in Portals
Even though a portal renders content outside the parent DOM, events still bubble up through the React component tree as if the portal were a normal child.
Example:
import { createRoot } from 'react-dom/client';
import { useState } from 'react';
import { createPortal } from 'react-dom';
function PortalButton({ onClick, children }) {
return createPortal(
<buttononClick={onClick}
style={{
position: 'fixed',
bottom: '20px',
right: '20px',
padding: '10px',
background: 'blue',
color: 'white'
}}>
{children}
</button>,
document.body
);
}
function App() {
const [divClicks, setDivClicks] = useState(0);
const [buttonClicks, setButtonClicks] = useState(0);
return (
<divstyle={{ padding: '20px', border: '2px solid black', margin: '20px' }}
onClick={() => setDivClicks(c => c + 1)}
>
<h2>Div Clicked: {divClicks}</h2>
<h2>Button Clicked: {buttonClicks}</h2>
<PortalButton onClick={() => setButtonClicks(c => c + 1)}>
Floating Button
</PortalButton>
</div>
);
}
createRoot(document.getElementById('root')).render(<App />);
- Clicking the portal button triggers its own click handler.
- Events still bubble to the parent div, showing that React’s event system works normally with portals.
Why Use React Portals?
- Clean DOM Structure – Avoid unnecessary wrappers in the main DOM.
- Overlay UI Components – Modals, tooltips, dropdowns, and notifications.
- Event Propagation Works Normally – Events behave as if components were inside the root DOM.
- Maintain React State – Portal components can still manage state via props or hooks.
- Break Out of Layout Constraints – Works well with parents having
overflow: hidden, z-index conflicts, or complex CSS layouts.
