Building Modal Dialogs and Overlays with Portals0%
Building Modal Dialogs and Overlays with Portals
Beginner12 min read•Updated: Jul 11, 2026
Building Modal Dialogs and Overlays with Portals
1. Anatomy of a Production Modal
A production-grade modal dialog is more than just a centered box. It requires:
- 1Physical DOM Portaling: Mounting to
document.bodyviacreatePortal. - 2Backdrop / Overlay: Dimming the underlying interface and closing on backdrop click.
- 3Escape Key Dismissal: Listening for the
Escapekey to close the modal. - 4Body Scroll Locking: Preventing the background page from scrolling while the modal is open.
- 5Accessibility: Using semantic ARIA attributes (
role="dialog",aria-modal="true").
Output
┌────────────────────────────────────────────────────────┐
│ Darkened Backdrop Overlay (z-50) │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Modal Dialog Container │ │
│ │ │ │
│ │ Header: Confirm Action [ ✕ ] │ │
│ │ Body: Are you sure you want to enroll? │ │
│ │ │ │
│ │ Footer: [ Cancel ] [ Confirm ] │ │
│ └────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
2. Complete Modal Implementation
JSX
// src/components/Modal.jsx
import React, { useEffect } from 'react';
import { createPortal } from 'react-dom';
export default function Modal({ isOpen, onClose, title, children }) {
// 1. Close on Escape Key & Lock Body Scroll
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
onClose();
}
};
// Lock background page scroll
document.body.style.overflow = 'hidden';
window.addEventListener('keydown', handleKeyDown);
return () => {
// Restore scroll and remove listener on cleanup
document.body.style.overflow = 'unset';
window.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);
// If closed, render nothing
if (!isOpen) return null;
// 2. Portal to document.body
return createPortal(
<div
className="modal-backdrop"
onClick={onClose} // Close when clicking darkened backdrop
role="presentation"
>
<div
className="modal-content"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
onClick={(e) => e.stopPropagation()} // Prevent backdrop click from triggering inside card!
>
<header className="modal-header">
<h3 id="modal-title">{title}</h3>
<button
type="button"
onClick={onClose}
className="btn-close"
aria-label="Close dialog"
>
✕
</button>
</header>
<div className="modal-body">{children}</div>
</div>
</div>,
document.body
);
}
3. Accompanying CSS Styles
CSS
/* Modal Backdrop Overlay */
.modal-backdrop {
position: fixed;
inset: 0;
background-color: rgba(15, 23, 42, 0.65); /* Darkened blur */
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 16px;
}
/* Modal Card */
.modal-content {
background-color: #ffffff;
border-radius: 16px;
width: 100%;
max-width: 500px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
animation: modalFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes modalFadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
4. The Critical e.stopPropagation() Nuance
Notice line 35: onClick={(e) => e.stopPropagation()} on .modal-content.
Because the outer .modal-backdrop has an onClick={onClose} handler, clicking anywhere inside the modal card would bubble up to the backdrop and close the modal! Invoking e.stopPropagation() on the inner container halts event bubbling, ensuring the modal only closes when the user clicks outside the card on the darkened backdrop.
Interactive Quiz
Practice Quiz
Question 1
Why is
e.stopPropagation() called on the inner modal card container?Question 2
Why should
document.body.style.overflow = 'hidden' be set when a modal opens?Question 3
What must be done in the
useEffect cleanup function when locking body scroll?Question 4
Which standard keyboard key should dismiss accessible modal overlays?
Question 5
What ARIA attributes should be added to an accessible modal dialog?
Next Lesson
Event Bubbling Through Portals Explained
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| Breaking Out of DOM Hierarchies with createPortal | Event Bubbling Through Portals Explained |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.