
createPortal (React)
Using the react dom create portal to lift dom elements into the root.
## Requirements
React's createPortal API allows you to render elements outside of their parent component's DOM hierarchy. This is especially useful when dealing with UI elements like modals, tooltips, dropdowns, and overlays that must break out of the normal rendering flow.
## 📦 Requirements
To use createPortal, make sure your project meets these requirements:
- React 16 or higher
react-dominstalled- An additional DOM node (e.g. a
div#modal-root) defined in your base HTML
Install React and ReactDOM if you haven't already:
npm install react react-domInstall type in-case of typescript.
npm install -D @types/react @types/react-dom## 🏠 HTML Setup
First, add a portal target to your HTML:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<!-- Portal targets -->
<div id="modal-root"></div>
<div id="tooltip-root"></div>
</body>
</html>## 🎯 Basic Portal Example
import * as React from 'react';
import { createPortal } from 'react-dom';
type ModalProps = {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
};
export function Modal({ children, isOpen, onClose }: ModalProps) {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [onClose]);
if (!isOpen) return null;
const modalRoot = document.getElementById('modal-root');
if (!modalRoot) {
console.error('Modal root element not found');
return null;
}
return createPortal(
<div
style={{
position: 'fixed',
inset: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000
}}
onClick={onClose}
role="dialog"
aria-modal="true"
>
<div
style={{ background: 'white', padding: '20px', borderRadius: '8px', maxWidth: '500px', width: '90%' }}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
>
<button
onClick={onClose}
style={{
float: 'right',
background: 'none',
border: 'none',
fontSize: '1.5rem',
cursor: 'pointer'
}}
>
x
</button>
{children}
</div>
</div>,
modalRoot
);
}import { useState } from 'react';
import { Modal } from './modal.tsx';
export default function App() {
const [isOpen, setIsOpen] = useState<boolean>(false);
return (
<div style={{ padding: '20px' }}>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<div style={{ overflow: 'hidden', height: '200px', border: '1px solid #ccc' }}>
This container has overflow hidden, but the modal escapes it!
</div>
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
>
<h2>Modal Title</h2>
<p>This modal renders outside the main React root!</p>
</Modal>
</div>
);
}## 📝 Key Takeaways
- Type Safety: Use TypeScript interfaces to define clear prop contracts
- Error Handling: Always check if portal target exists and handle gracefully
- Performance: Conditionally render portals to avoid unnecessary DOM operations
- Accessibility: Add proper ARIA attributes for modals and dialogs
- Event Bubbling: Remember events bubble through React tree, not DOM tree
- Cleanup: Properly cleanup event listeners and portal nodes
Portals are powerful tools that help you create professional, accessible UI components that break out of container constraints while maintaining React's component architecture and TypeScript's type safety.
Published on October 23, 2025
3 min read
Found an Issue!
Find an issue with this post? Think you could clarify, update or add something? All my posts are available to edit on Github. Any fix, little or small, is appreciated!
Edit on GitHub