Modal & Popup Components
A comprehensive collection of modern modal dialogs and popup components with smooth animations, accessibility features, and customizable designs
Responsive Design
Yes
Dark Mode Support
No
lines
11
Browser Compatibility
No
Live Preview
Interact with the component without leaving the page.
Modal & Popup Components
A comprehensive collection of modern modal dialogs and popup components featuring smooth animations, accessibility support, keyboard navigation, and customizable designs for enhanced user interaction.
Features
- Multiple Modal Types: Basic modals, confirmation dialogs, image galleries, and forms
- Smooth Animations: CSS-based transitions with customizable timing
- Accessibility Support: ARIA attributes, keyboard navigation, and focus management
- Responsive Design: Adapts to all screen sizes and orientations
- Backdrop Control: Customizable backdrop behavior and click-to-close
- Stacking Support: Multiple modals with proper z-index management
- Auto-positioning: Smart positioning to avoid viewport overflow
- Modern Design: Clean, minimalist aesthetics with subtle effects
Demo
<div class="modal-demo-container">
<div class="modal-triggers">
<h3>Modal Types</h3>
<div class="trigger-grid">
<button class="trigger-btn" data-modal="basic-modal">
<span class="trigger-icon">📄</span>
Basic Modal
</button>
<button class="trigger-btn" data-modal="confirm-modal">
<span class="trigger-icon">❓</span>
Confirmation
</button>
<button class="trigger-btn" data-modal="form-modal">
<span class="trigger-icon">📝</span>
Form Modal
</button>
<button class="trigger-btn" data-modal="image-modal">
<span class="trigger-icon">🖼️</span>
Image Gallery
</button>
<button class="trigger-btn" data-modal="video-modal">
<span class="trigger-icon">🎥</span>
Video Modal
</button>
<button class="trigger-btn" data-modal="fullscreen-modal">
<span class="trigger-icon">⛶</span>
Fullscreen
</button>
</div>
</div>
<div class="popup-triggers">
<h3>Popup Types</h3>
<div class="trigger-grid">
<button class="trigger-btn" data-popup="tooltip-popup">
<span class="trigger-icon">💬</span>
Tooltip
</button>
<button class="trigger-btn" data-popup="notification-popup">
<span class="trigger-icon">🔔</span>
Notification
</button>
<button class="trigger-btn" data-popup="dropdown-popup">
<span class="trigger-icon">📋</span>
Dropdown
</button>
<button class="trigger-btn" data-popup="context-popup">
<span class="trigger-icon">⚙️</span>
Context Menu
</button>
</div>
</div>
</div>
<div class="modal-overlay" id="basic-modal">
<div class="modal-container">
<div class="modal-header">
<h2 class="modal-title">Basic Modal</h2>
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<p>This is a basic modal dialog with smooth animations and accessibility features. It includes proper focus management and keyboard navigation support.</p>
<p>You can customize the appearance, animations, and behavior to match your design requirements.</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancel</button>
<button class="btn btn-primary">Confirm</button>
</div>
</div>
</div>
<div class="modal-overlay" id="confirm-modal">
<div class="modal-container modal-small">
<div class="modal-header">
<div class="modal-icon warning">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
</svg>
</div>
<h2 class="modal-title">Confirm Action</h2>
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancel</button>
<button class="btn btn-danger">Delete</button>
</div>
</div>
</div>
<div class="modal-overlay" id="form-modal">
<div class="modal-container">
<div class="modal-header">
<h2 class="modal-title">Contact Form</h2>
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<form class="modal-form">
<div class="form-group">
<label for="modal-name">Name</label>
<input type="text" id="modal-name" name="name" required>
</div>
<div class="form-group">
<label for="modal-email">Email</label>
<input type="email" id="modal-email" name="email" required>
</div>
<div class="form-group">
<label for="modal-message">Message</label>
<textarea id="modal-message" name="message" rows="4" required></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancel</button>
<button class="btn btn-primary" type="submit">Send Message</button>
</div>
</div>
</div>
<div class="modal-overlay" id="image-modal">
<div class="modal-container modal-image">
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
<div class="image-gallery">
<div class="gallery-main">
<img src="https://picsum.photos/800/600?random=1" alt="Gallery image" class="gallery-image">
<div class="gallery-controls">
<button class="gallery-btn prev" aria-label="Previous image">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
</svg>
</button>
<button class="gallery-btn next" aria-label="Next image">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
</svg>
</button>
</div>
</div>
<div class="gallery-info">
<h3>Image Gallery</h3>
<p>Image 1 of 5</p>
</div>
</div>
</div>
</div>
<div class="modal-overlay" id="video-modal">
<div class="modal-container modal-video">
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
<div class="video-container">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
frameborder="0"
allowfullscreen
title="Video player">
</iframe>
</div>
</div>
</div>
<div class="modal-overlay" id="fullscreen-modal">
<div class="modal-container modal-fullscreen">
<div class="modal-header">
<h2 class="modal-title">Fullscreen Modal</h2>
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="fullscreen-content">
<h3>Fullscreen Experience</h3>
<p>This modal takes up the entire viewport, perfect for immersive content, detailed forms, or complex interfaces.</p>
<div class="content-grid">
<div class="content-card">
<h4>Feature 1</h4>
<p>Detailed description of the first feature with comprehensive information.</p>
</div>
<div class="content-card">
<h4>Feature 2</h4>
<p>Detailed description of the second feature with comprehensive information.</p>
</div>
<div class="content-card">
<h4>Feature 3</h4>
<p>Detailed description of the third feature with comprehensive information.</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Close</button>
<button class="btn btn-primary">Save Changes</button>
</div>
</div>
</div>
<div class="popup-container" id="tooltip-popup">
<div class="popup-content tooltip">
<div class="tooltip-arrow"></div>
<p>This is a helpful tooltip that provides additional information about the element.</p>
</div>
</div>
<div class="popup-container" id="notification-popup">
<div class="popup-content notification success">
<div class="notification-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
</div>
<div class="notification-content">
<h4>Success!</h4>
<p>Your action has been completed successfully.</p>
</div>
<button class="notification-close" aria-label="Close notification">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
</div>
<div class="popup-container" id="dropdown-popup">
<div class="popup-content dropdown">
<div class="dropdown-header">
<h4>Options</h4>
</div>
<div class="dropdown-list">
<a href="#" class="dropdown-item">
<span class="dropdown-icon">📄</span>
View Details
</a>
<a href="#" class="dropdown-item">
<span class="dropdown-icon">✏️</span>
Edit Item
</a>
<a href="#" class="dropdown-item">
<span class="dropdown-icon">📋</span>
Copy Link
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item danger">
<span class="dropdown-icon">🗑️</span>
Delete Item
</a>
</div>
</div>
</div>
<div class="popup-container" id="context-popup">
<div class="popup-content context-menu">
<div class="context-list">
<button class="context-item">
<span class="context-icon">📋</span>
Copy
<span class="context-shortcut">Ctrl+C</span>
</button>
<button class="context-item">
<span class="context-icon">📄</span>
Paste
<span class="context-shortcut">Ctrl+V</span>
</button>
<button class="context-item">
<span class="context-icon">✂️</span>
Cut
<span class="context-shortcut">Ctrl+X</span>
</button>
<div class="context-divider"></div>
<button class="context-item">
<span class="context-icon">🔄</span>
Refresh
<span class="context-shortcut">F5</span>
</button>
<button class="context-item">
<span class="context-icon">⚙️</span>
Settings
</button>
</div>
</div>
</div>
.modal-demo-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.modal-triggers,
.popup-triggers {
margin-bottom: 3rem;
padding: 2rem;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
}
.modal-triggers h3,
.popup-triggers h3 {
margin: 0 0 2rem 0;
font-size: 1.5rem;
font-weight: 700;
color: #1a1a1a;
text-align: center;
}
.trigger-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.trigger-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 1.5rem;
border: 2px solid #e5e7eb;
background: white;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
color: #374151;
}
.trigger-btn:hover {
border-color: #3b82f6;
background: #f8fafc;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
.trigger-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
padding: 1rem;
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal-overlay.active .modal-container {
transform: scale(1) translateY(0);
opacity: 1;
}.modal-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
max-width: 500px;
width: 100%;
max-height: 90vh;
overflow: hidden;
transform: scale(0.9) translateY(20px);
opacity: 0;
transition: all 0.3s ease;
position: relative;
}
.modal-container.modal-small {
max-width: 400px;
}
.modal-container.modal-large {
max-width: 800px;
}
.modal-container.modal-fullscreen {
max-width: 95vw;
max-height: 95vh;
width: 95vw;
height: 95vh;
}
.modal-container.modal-image {
max-width: 90vw;
max-height: 90vh;
background: transparent;
box-shadow: none;
}
.modal-container.modal-video {
max-width: 80vw;
max-height: 80vh;
background: #000;
border-radius: 8px;
}.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e7eb;
background: #f9fafb;
}
.modal-title {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
color: #1a1a1a;
display: flex;
align-items: center;
gap: 0.75rem;
}
.modal-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
}
.modal-icon.warning {
background: #fef3c7;
color: #d97706;
}
.modal-icon.success {
background: #d1fae5;
color: #059669;
}
.modal-icon.error {
background: #fee2e2;
color: #dc2626;
}
.modal-icon svg {
width: 20px;
height: 20px;
}
.modal-close {
width: 40px;
height: 40px;
border: none;
background: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #6b7280;
transition: all 0.2s ease;
}
.modal-close:hover {
background: #f3f4f6;
color: #374151;
}
.modal-close svg {
width: 20px;
height: 20px;
}.modal-body {
padding: 2rem;
overflow-y: auto;
max-height: 60vh;
}
.modal-body p {
margin: 0 0 1rem 0;
line-height: 1.6;
color: #374151;
}
.modal-body p:last-child {
margin-bottom: 0;
}.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 1rem;
padding: 1.5rem 2rem;
border-top: 1px solid #e5e7eb;
background: #f9fafb;
}.modal-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: 600;
color: #374151;
font-size: 0.875rem;
}
.form-group input,
.form-group textarea {
padding: 0.75rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
transform: translateY(-1px);
}
.btn-secondary {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
}
.btn-secondary:hover {
background: #e5e7eb;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-1px);
}.image-gallery {
position: relative;
background: #000;
border-radius: 8px;
overflow: hidden;
}
.gallery-main {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
.gallery-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.gallery-controls {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
padding: 0 1rem;
pointer-events: none;
}
.gallery-btn {
width: 50px;
height: 50px;
border: none;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
pointer-events: auto;
}
.gallery-btn:hover {
background: white;
transform: scale(1.1);
}
.gallery-btn svg {
width: 24px;
height: 24px;
color: #374151;
}
.gallery-info {
padding: 1rem;
background: rgba(0, 0, 0, 0.8);
color: white;
text-align: center;
}
.gallery-info h3 {
margin: 0 0 0.5rem 0;
font-size: 1.125rem;
}
.gallery-info p {
margin: 0;
color: #d1d5db;
font-size: 0.875rem;
}.video-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}.fullscreen-content {
padding: 2rem 0;
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.content-card {
padding: 2rem;
background: #f9fafb;
border-radius: 12px;
border: 1px solid #e5e7eb;
}
.content-card h4 {
margin: 0 0 1rem 0;
font-size: 1.125rem;
font-weight: 700;
color: #1a1a1a;
}
.content-card p {
margin: 0;
color: #6b7280;
line-height: 1.6;
}.popup-container {
position: fixed;
z-index: 1100;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
pointer-events: none;
}
.popup-container.active {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.popup-content {
background: white;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
transform: scale(0.95) translateY(-10px);
transition: all 0.2s ease;
}
.popup-container.active .popup-content {
transform: scale(1) translateY(0);
}.tooltip {
padding: 0.75rem 1rem;
max-width: 250px;
font-size: 0.875rem;
color: #374151;
line-height: 1.4;
position: relative;
}
.tooltip-arrow {
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
}.notification {
padding: 1rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
min-width: 300px;
max-width: 400px;
position: relative;
}
.notification.success {
background: #f0fdf4;
border-left: 4px solid #22c55e;
}
.notification.warning {
background: #fffbeb;
border-left: 4px solid #f59e0b;
}
.notification.error {
background: #fef2f2;
border-left: 4px solid #ef4444;
}
.notification-icon {
width: 24px;
height: 24px;
flex-shrink: 0;
}
.notification.success .notification-icon {
color: #22c55e;
}
.notification.warning .notification-icon {
color: #f59e0b;
}
.notification.error .notification-icon {
color: #ef4444;
}
.notification-content h4 {
margin: 0 0 0.25rem 0;
font-size: 0.875rem;
font-weight: 600;
color: #1a1a1a;
}
.notification-content p {
margin: 0;
font-size: 0.875rem;
color: #6b7280;
}
.notification-close {
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 24px;
height: 24px;
border: none;
background: none;
cursor: pointer;
color: #9ca3af;
transition: color 0.2s ease;
}
.notification-close:hover {
color: #6b7280;
}
.notification-close svg {
width: 16px;
height: 16px;
}.dropdown {
min-width: 200px;
padding: 0.5rem 0;
}
.dropdown-header {
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 0.5rem;
}
.dropdown-header h4 {
margin: 0;
font-size: 0.875rem;
font-weight: 600;
color: #374151;
}
.dropdown-list {
display: flex;
flex-direction: column;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
text-decoration: none;
color: #374151;
font-size: 0.875rem;
transition: background-color 0.2s ease;
}
.dropdown-item:hover {
background: #f3f4f6;
}
.dropdown-item.danger {
color: #dc2626;
}
.dropdown-item.danger:hover {
background: #fef2f2;
}
.dropdown-icon {
font-size: 1rem;
}
.dropdown-divider {
height: 1px;
background: #e5e7eb;
margin: 0.5rem 0;
}.context-menu {
min-width: 180px;
padding: 0.5rem 0;
}
.context-list {
display: flex;
flex-direction: column;
}
.context-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
border: none;
background: none;
text-align: left;
cursor: pointer;
font-size: 0.875rem;
color: #374151;
transition: background-color 0.2s ease;
}
.context-item:hover {
background: #f3f4f6;
}
.context-icon {
margin-right: 0.75rem;
font-size: 1rem;
}
.context-shortcut {
font-size: 0.75rem;
color: #9ca3af;
}
.context-divider {
height: 1px;
background: #e5e7eb;
margin: 0.5rem 0;
}.dark .modal-container {
background: #1f2937;
color: #e5e7eb;
}
.dark .modal-header,
.dark .modal-footer {
background: #111827;
border-color: #374151;
}
.dark .modal-title {
color: #f9fafb;
}
.dark .modal-body p {
color: #d1d5db;
}
.dark .form-group input,
.dark .form-group textarea {
background: #374151;
border-color: #4b5563;
color: #e5e7eb;
}
.dark .form-group input:focus,
.dark .form-group textarea:focus {
border-color: #3b82f6;
}
.dark .content-card {
background: #111827;
border-color: #374151;
}
.dark .popup-content {
background: #1f2937;
border-color: #374151;
color: #e5e7eb;
}
.dark .dropdown-item,
.dark .context-item {
color: #d1d5db;
}
.dark .dropdown-item:hover,
.dark .context-item:hover {
background: #374151;
}@media (max-width: 768px) {
.modal-demo-container {
padding: 1rem;
}
.modal-triggers,
.popup-triggers {
padding: 1.5rem;
}
.trigger-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.modal-overlay {
padding: 0.5rem;
}
.modal-container {
max-width: 100%;
margin: 0;
}
.modal-container.modal-fullscreen {
max-width: 100vw;
max-height: 100vh;
width: 100vw;
height: 100vh;
border-radius: 0;
}
.modal-header,
.modal-footer {
padding: 1rem 1.5rem;
}
.modal-body {
padding: 1.5rem;
}
.content-grid {
grid-template-columns: 1fr;
}
.notification {
min-width: 280px;
max-width: 320px;
}
}
@media (max-width: 480px) {
.trigger-grid {
grid-template-columns: 1fr;
}
.trigger-btn {
padding: 1rem;
}
.modal-header,
.modal-footer {
padding: 1rem;
}
.modal-body {
padding: 1rem;
}
.modal-footer {
flex-direction: column;
gap: 0.5rem;
}
.btn {
width: 100%;
}
.gallery-controls {
padding: 0 0.5rem;
}
.gallery-btn {
width: 40px;
height: 40px;
}
}@media (prefers-reduced-motion: reduce) {
.modal-overlay,
.modal-container,
.popup-container,
.popup-content {
transition: none;
}
.modal-overlay.active .modal-container {
transform: none;
}
.popup-container.active .popup-content {
transform: none;
}
}.modal-close:focus,
.btn:focus,
.dropdown-item:focus,
.context-item:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}.fade-in {
animation: fadeIn 0.3s ease;
}
.slide-up {
animation: slideUp 0.3s ease;
}
.scale-in {
animation: scaleIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
class ModalPopupComponents {
constructor() {
this.modals = new Map();
this.popups = new Map();
this.activeModal = null;
this.activePopup = null;
this.modalStack = [];
this.focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
this.init();
}
init() {
this.setupEventListeners();
this.initializeModals();
this.initializePopups();
this.setupKeyboardNavigation();
this.setupAccessibility();
}
setupEventListeners() {
document.addEventListener('click', (e) => {
const trigger = e.target.closest('[data-modal]');
if (trigger) {
e.preventDefault();
const modalId = trigger.dataset.modal;
this.openModal(modalId);
}
const popupTrigger = e.target.closest('[data-popup]');
if (popupTrigger) {
e.preventDefault();
const popupId = popupTrigger.dataset.popup;
this.openPopup(popupId, popupTrigger);
}
if (e.target.closest('.modal-close, .modal-cancel')) {
e.preventDefault();
this.closeActiveModal();
}
if (e.target.closest('.notification-close')) {
e.preventDefault();
this.closeActivePopup();
}
if (e.target.classList.contains('modal-overlay')) {
this.closeActiveModal();
}
});
document.addEventListener('contextmenu', (e) => {
const contextTrigger = e.target.closest('[data-context]');
if (contextTrigger) {
e.preventDefault();
this.openContextMenu(e, 'context-popup');
}
});
document.addEventListener('click', (e) => {
if (this.activePopup && !e.target.closest('.popup-container')) {
this.closeActivePopup();
}
});
}
initializeModals() {
const modalElements = document.querySelectorAll('.modal-overlay');
modalElements.forEach(modal => {
const modalId = modal.id;
this.modals.set(modalId, {
element: modal,
container: modal.querySelector('.modal-container'),
isOpen: false,
previousFocus: null
});
});
}
initializePopups() {
const popupElements = document.querySelectorAll('.popup-container');
popupElements.forEach(popup => {
const popupId = popup.id;
this.popups.set(popupId, {
element: popup,
content: popup.querySelector('.popup-content'),
isOpen: false,
trigger: null
});
});
}
setupKeyboardNavigation() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (this.activePopup) {
this.closeActivePopup();
} else if (this.activeModal) {
this.closeActiveModal();
}
}
if (e.key === 'Tab' && this.activeModal) {
this.handleTabNavigation(e);
}
});
}
setupAccessibility() {
this.modals.forEach((modal, id) => {
modal.element.setAttribute('role', 'dialog');
modal.element.setAttribute('aria-modal', 'true');
modal.element.setAttribute('aria-hidden', 'true');
const title = modal.element.querySelector('.modal-title');
if (title) {
title.id = `${id}-title`;
modal.element.setAttribute('aria-labelledby', `${id}-title`);
}
});
this.popups.forEach((popup, id) => {
popup.element.setAttribute('role', 'tooltip');
popup.element.setAttribute('aria-hidden', 'true');
});
}
openModal(modalId, options = {}) {
const modal = this.modals.get(modalId);
if (!modal || modal.isOpen) return;
modal.previousFocus = document.activeElement;
if (this.activePopup) {
this.closeActivePopup();
}
this.modalStack.push(modalId);
this.activeModal = modalId;
modal.element.style.display = 'flex';
modal.element.setAttribute('aria-hidden', 'false');
requestAnimationFrame(() => {
modal.element.classList.add('active');
if (options.animation) {
modal.container.classList.add(options.animation);
}
});
setTimeout(() => {
this.focusFirstElement(modal.element);
}, 100);
document.body.style.overflow = 'hidden';
modal.isOpen = true;
this.dispatchEvent('modalOpen', { modalId, modal: modal.element });
}
closeModal(modalId) {
const modal = this.modals.get(modalId);
if (!modal || !modal.isOpen) return;
const stackIndex = this.modalStack.indexOf(modalId);
if (stackIndex > -1) {
this.modalStack.splice(stackIndex, 1);
}
this.activeModal = this.modalStack.length > 0 ? this.modalStack[this.modalStack.length - 1] : null;
modal.element.classList.remove('active');
modal.element.setAttribute('aria-hidden', 'true');
setTimeout(() => {
modal.element.style.display = 'none';
modal.container.className = modal.container.className.replace(/\b(fade-in|slide-up|scale-in)\b/g, '');
}, 300);
if (modal.previousFocus) {
modal.previousFocus.focus();
modal.previousFocus = null;
}
if (this.modalStack.length === 0) {
document.body.style.overflow = '';
}
modal.isOpen = false;
this.dispatchEvent('modalClose', { modalId, modal: modal.element });
}
closeActiveModal() {
if (this.activeModal) {
this.closeModal(this.activeModal);
}
}
openPopup(popupId, trigger, options = {}) {
const popup = this.popups.get(popupId);
if (!popup || popup.isOpen) return;
if (this.activePopup) {
this.closeActivePopup();
}
this.activePopup = popupId;
popup.trigger = trigger;
this.positionPopup(popup, trigger, options.position);
popup.element.style.display = 'block';
popup.element.setAttribute('aria-hidden', 'false');
requestAnimationFrame(() => {
popup.element.classList.add('active');
});
popup.isOpen = true;
if (popup.element.querySelector('.notification') && options.autoClose !== false) {
setTimeout(() => {
this.closePopup(popupId);
}, options.duration || 5000);
}
this.dispatchEvent('popupOpen', { popupId, popup: popup.element, trigger });
}
closePopup(popupId) {
const popup = this.popups.get(popupId);
if (!popup || !popup.isOpen) return;
popup.element.classList.remove('active');
popup.element.setAttribute('aria-hidden', 'true');
setTimeout(() => {
popup.element.style.display = 'none';
}, 200);
if (this.activePopup === popupId) {
this.activePopup = null;
}
popup.isOpen = false;
popup.trigger = null;
this.dispatchEvent('popupClose', { popupId, popup: popup.element });
}
closeActivePopup() {
if (this.activePopup) {
this.closePopup(this.activePopup);
}
}
openContextMenu(event, popupId) {
const popup = this.popups.get(popupId);
if (!popup) return;
if (this.activePopup) {
this.closeActivePopup();
}
this.activePopup = popupId;
popup.element.style.position = 'fixed';
popup.element.style.left = `${event.clientX}px`;
popup.element.style.top = `${event.clientY}px`;
const rect = popup.content.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
if (event.clientX + rect.width > viewportWidth) {
popup.element.style.left = `${event.clientX - rect.width}px`;
}
if (event.clientY + rect.height > viewportHeight) {
popup.element.style.top = `${event.clientY - rect.height}px`;
}
popup.element.style.display = 'block';
popup.element.setAttribute('aria-hidden', 'false');
requestAnimationFrame(() => {
popup.element.classList.add('active');
});
popup.isOpen = true;
}
positionPopup(popup, trigger, position = 'bottom') {
const triggerRect = trigger.getBoundingClientRect();
const popupRect = popup.content.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const scrollX = window.pageXOffset;
const scrollY = window.pageYOffset;
let left, top;
switch (position) {
case 'top':
left = triggerRect.left + (triggerRect.width / 2) - (popupRect.width / 2);
top = triggerRect.top - popupRect.height - 10;
break;
case 'bottom':
left = triggerRect.left + (triggerRect.width / 2) - (popupRect.width / 2);
top = triggerRect.bottom + 10;
break;
case 'left':
left = triggerRect.left - popupRect.width - 10;
top = triggerRect.top + (triggerRect.height / 2) - (popupRect.height / 2);
break;
case 'right':
left = triggerRect.right + 10;
top = triggerRect.top + (triggerRect.height / 2) - (popupRect.height / 2);
break;
default:
left = triggerRect.left;
top = triggerRect.bottom + 10;
}
if (left < 0) left = 10;
if (left + popupRect.width > viewportWidth) left = viewportWidth - popupRect.width - 10;
if (top < 0) top = 10;
if (top + popupRect.height > viewportHeight) top = viewportHeight - popupRect.height - 10;
popup.element.style.position = 'fixed';
popup.element.style.left = `${left}px`;
popup.element.style.top = `${top}px`;
}
handleTabNavigation(event) {
const modal = this.modals.get(this.activeModal);
if (!modal) return;
const focusableElements = modal.element.querySelectorAll(this.focusableElements);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (event.shiftKey) {
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
}
focusFirstElement(container) {
const focusableElements = container.querySelectorAll(this.focusableElements);
if (focusableElements.length > 0) {
focusableElements[0].focus();
}
}
dispatchEvent(eventName, detail) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
}
show(id, options = {}) {
if (this.modals.has(id)) {
this.openModal(id, options);
} else if (this.popups.has(id)) {
const trigger = options.trigger || document.body;
this.openPopup(id, trigger, options);
}
}
hide(id) {
if (this.modals.has(id)) {
this.closeModal(id);
} else if (this.popups.has(id)) {
this.closePopup(id);
}
}
hideAll() {
this.modalStack.forEach(modalId => {
this.closeModal(modalId);
});
if (this.activePopup) {
this.closeActivePopup();
}
}
isOpen(id) {
const modal = this.modals.get(id);
const popup = this.popups.get(id);
return (modal && modal.isOpen) || (popup && popup.isOpen);
}
showNotification(message, type = 'success', options = {}) {
const notification = this.createNotification(message, type, options);
document.body.appendChild(notification);
this.positionNotification(notification, options.position);
requestAnimationFrame(() => {
notification.classList.add('active');
});
setTimeout(() => {
notification.classList.remove('active');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, options.duration || 5000);
return notification;
}
createNotification(message, type, options) {
const notification = document.createElement('div');
notification.className = `popup-container notification-popup`;
const icons = {
success: '<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>',
warning: '<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>',
error: '<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>'
};
notification.innerHTML = `
<div class="popup-content notification ${type}">
<div class="notification-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
${icons[type] || icons.success}
</svg>
</div>
<div class="notification-content">
<h4>${options.title || type.charAt(0).toUpperCase() + type.slice(1)}</h4>
<p>${message}</p>
</div>
<button class="notification-close" aria-label="Close notification">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
`;
notification.querySelector('.notification-close').addEventListener('click', () => {
notification.classList.remove('active');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
});
return notification;
}
positionNotification(notification, position = 'top-right') {
const positions = {
'top-left': { top: '20px', left: '20px' },
'top-right': { top: '20px', right: '20px' },
'top-center': { top: '20px', left: '50%', transform: 'translateX(-50%)' },
'bottom-left': { bottom: '20px', left: '20px' },
'bottom-right': { bottom: '20px', right: '20px' },
'bottom-center': { bottom: '20px', left: '50%', transform: 'translateX(-50%)' }
};
Object.assign(notification.style, {
position: 'fixed',
zIndex: '1200',
...positions[position]
});
}
destroy() {
this.hideAll();
document.body.style.overflow = '';
}
}
const modalPopupSystem = new ModalPopupComponents();
if (typeof module !== 'undefined' && module.exports) {
module.exports = ModalPopupComponents;
} else if (typeof window !== 'undefined') {
window.ModalPopupComponents = ModalPopupComponents;
window.modalPopupSystem = modalPopupSystem;
}
Usage Examples
Basic Modal
modalPopupSystem.show('basic-modal');
modalPopupSystem.hide('basic-modal');
if (modalPopupSystem.isOpen('basic-modal')) {
console.log('Modal is open');
}
Notification System
modalPopupSystem.showNotification('Operation completed successfully!', 'success');
modalPopupSystem.showNotification('Please check your input', 'warning', {
title: 'Validation Error',
duration: 3000,
position: 'top-center'
});
modalPopupSystem.showNotification('Something went wrong', 'error', {
duration: 0 // Don't auto-close
});
Event Handling
document.addEventListener('modalOpen', (e) => {
console.log('Modal opened:', e.detail.modalId);
});
document.addEventListener('modalClose', (e) => {
console.log('Modal closed:', e.detail.modalId);
});
document.addEventListener('popupOpen', (e) => {
console.log('Popup opened:', e.detail.popupId);
});
Custom Modal Creation
function createCustomModal(id, title, content) {
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = id;
modal.innerHTML = `
<div class="modal-container">
<div class="modal-header">
<h2 class="modal-title">${title}</h2>
<button class="modal-close" aria-label="Close modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
${content}
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancel</button>
<button class="btn btn-primary">Confirm</button>
</div>
</div>
`;
document.body.appendChild(modal);
modalPopupSystem.modals.set(id, {
element: modal,
container: modal.querySelector('.modal-container'),
isOpen: false,
previousFocus: null
});
return modal;
}
API Reference
Methods
| Method | Parameters | Description |
|---|---|---|
show(id, options) | id: string, options: object | Opens a modal or popup |
hide(id) | id: string | Closes a specific modal or popup |
hideAll() | - | Closes all open modals and popups |
isOpen(id) | id: string | Returns boolean indicating if modal/popup is open |
showNotification(message, type, options) | message: string, type: string, options: object | Shows a notification |
destroy() | - | Cleans up the system and closes all modals |
Events
| Event | Detail | Description |
|---|---|---|
modalOpen | { modalId, modal } | Fired when a modal opens |
modalClose | { modalId, modal } | Fired when a modal closes |
popupOpen | { popupId, popup, trigger } | Fired when a popup opens |
popupClose | { popupId, popup } | Fired when a popup closes |
Options
Modal Options
{
animation: 'fade-in' | 'slide-up' | 'scale-in', // Animation type
backdrop: true | false, // Enable backdrop
keyboard: true | false, // Enable keyboard navigation
focus: true | false // Auto-focus first element
}
Popup Options
{
position: 'top' | 'bottom' | 'left' | 'right', // Popup position
trigger: HTMLElement, // Trigger element
autoClose: true | false, // Auto-close behavior
duration: number // Auto-close duration (ms)
}
Notification Options
{
title: string, // Notification title
duration: number, // Display duration (ms)
position: 'top-left' | 'top-right' | 'top-center' |
'bottom-left' | 'bottom-right' | 'bottom-center'
}
Customization
CSS Variables
:root {
--modal-backdrop-color: rgba(0, 0, 0, 0.5);
--modal-border-radius: 16px;
--modal-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
--modal-animation-duration: 0.3s;
--popup-border-radius: 8px;
--popup-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--notification-success-color: #22c55e;
--notification-warning-color: #f59e0b;
--notification-error-color: #ef4444;
}
Animation Customization
@keyframes slideInFromRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.modal-container.slide-right {
animation: slideInFromRight 0.3s ease;
}
Accessibility
- ARIA Support: Proper ARIA attributes for screen readers
- Keyboard Navigation: Full keyboard support with Tab and Escape keys
- Focus Management: Automatic focus trapping and restoration
- Screen Reader: Descriptive labels and announcements
- High Contrast: Support for high contrast mode
- Reduced Motion: Respects
prefers-reduced-motionsetting
Browser Support
- Modern Browsers: Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
- Mobile: iOS Safari 12+, Chrome Mobile 60+
- Features: CSS Grid, Flexbox, ES6 Classes, Custom Events
- Fallbacks: Graceful degradation for older browsers
Performance Considerations
- Lazy Loading: Modals are initialized only when needed
- Event Delegation: Efficient event handling with delegation
- Memory Management: Proper cleanup to prevent memory leaks
- Animation Optimization: CSS transforms for smooth animations
- Debouncing: Debounced resize and scroll events
Integration Examples
React Integration
x
import { useEffect, useRef } from 'react';
function ModalComponent({ isOpen, onClose, children }) {
const modalRef = useRef();
useEffect(() => {
if (isOpen) {
modalPopupSystem.show('react-modal');
} else {
modalPopupSystem.hide('react-modal');
}
}, [isOpen]);
useEffect(() => {
const handleModalClose = (e) => {
if (e.detail.modalId === 'react-modal') {
onClose();
}
};
document.addEventListener('modalClose', handleModalClose);
return () => document.removeEventListener('modalClose', handleModalClose);
}, [onClose]);
return (
<div className="modal-overlay" id="react-modal" ref={modalRef}>
<div className="modal-container">
{children}
</div>
</div>
);
}
Vue Integration
<template>
<div class="modal-overlay" :id="modalId" v-show="isOpen">
<div class="modal-container">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
modalId: String,
isOpen: Boolean
},
watch: {
isOpen(newVal) {
if (newVal) {
modalPopupSystem.show(this.modalId);
} else {
modalPopupSystem.hide(this.modalId);
}
}
},
mounted() {
document.addEventListener('modalClose', this.handleModalClose);
},
beforeDestroy() {
document.removeEventListener('modalClose', this.handleModalClose);
},
methods: {
handleModalClose(e) {
if (e.detail.modalId === this.modalId) {
this.$emit('close');
}
}
}
};
</script> HTML
4
lines
CSS
7
lines
<div class="modal-container">
<h2>Modal & Popup Components</h2>
<p>Modern modal dialogs and popup components</p>
</div>