Portals for Modals and Tooltips
Use ReactDOM.createPortal to render children into a different DOM node, enabling modals and tooltips that escape overflow:hidden containers.
Portals for Modals and Tooltips is a free Frontend Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Container Escape Problem
Modals and tooltips need to render at the top of the DOM, escaping overflow:hidden, z-index stacking, and transform-rooted contexts of their parent. Solution: portals.
createPortal API
createPortal(children, container) renders children into container (any DOM node) while keeping them logically children of the React component for events and context.
import { createPortal } from 'react-dom';
function Modal({ children, onClose }) {
return createPortal(
<div className="modal-backdrop" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}Portal Containers
Common targets: document.body, a dedicated #modal-root div in index.html, or a ref'd container created on demand.
<!-- index.html -->
<div id="root"></div>
<div id="modal-root"></div>
// component:
const modalRoot = document.getElementById('modal-root');
return createPortal(<div>...</div>, modalRoot);Why Not Just Render Inline?
If a parent has overflow: hidden, a child modal gets clipped. If a parent has transform: scale, the modal's fixed positioning becomes relative to the parent. Portals avoid all this.
Events Still Bubble
Despite rendering elsewhere in the DOM, React events bubble through the component tree, not the DOM tree. A click in the portal still bubbles to onClick handlers on ancestor React components.
Closing on Overlay Click
Stop event propagation on the modal content so clicks inside don't close the modal.
function Modal({ onClose, children }) {
return createPortal(
<div className="backdrop" onClick={onClose}>
<div className="content" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}Closing on Escape Key
Add a keydown listener while the modal is open, removed on unmount.
useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);Focus Management in Portals
When a modal opens: move focus inside, trap focus until close, then restore focus to the element that opened the modal. Use a library like focus-trap-react to avoid reimplementing this.
Native dialog Element
HTML's <dialog> handles modal positioning, backdrop, focus trap, and Escape close natively. For most modals it's a better starting point than a portal.
function Modal({ open, onClose, children }) {
const ref = useRef(null);
useEffect(() => {
if (open) ref.current?.showModal();
else ref.current?.close();
}, [open]);
return (
<dialog ref={ref} onClose={onClose}>
{children}
</dialog>
);
}Tooltips and Popovers
Tooltips position relative to an anchor element. Portal lets them escape stacking contexts. Combine with floating-ui for collision detection, flipping, and positioning.
import { useFloating, autoUpdate } from '@floating-ui/react';
function Tooltip({ children, content }) {
const { refs, floatingStyles } = useFloating({ whileElementsMounted: autoUpdate });
const [open, setOpen] = useState(false);
return (
<>
<button ref={refs.setReference} onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
{children}
</button>
{open && createPortal(
<div ref={refs.setFloating} style={floatingStyles}>{content}</div>,
document.body
)}
</>
);
}SSR Caveat
document doesn't exist on the server. Either render only on the client (check typeof window) or use Next.js's dynamic with ssr:false.
Accessibility: aria-modal
Modal containers should have role="dialog", aria-modal="true", and aria-labelledby pointing to the modal's heading. Or use native <dialog>.
Quick Check
Why use createPortal for modals instead of rendering them inline in the component tree?
Recap: Portals
createPortal(children, container) renders DOM elsewhere while keeping React tree logical. Solves overflow/transform/z-index escape problems. Events still bubble through React tree. Add Escape close + focus management. Native <dialog> handles much of this for modals. Combine with floating-ui for tooltips. Don't forget SSR (typeof window check).
Frequently asked questions
Is the “Portals for Modals and Tooltips” lesson free?
Yes — the full text of “Portals for Modals and Tooltips” is free to read here on the web, and the Frontend Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO.
What will I learn in “Portals for Modals and Tooltips”?
Use ReactDOM.createPortal to render children into a different DOM node, enabling modals and tooltips that escape overflow:hidden containers. You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Portals for Modals and Tooltips” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Compound Components with Context
- Render Props and HOC Patterns
- Portals for Modals and Tooltips
- Error Boundaries