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">
<!-- Modal Triggers -->
<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>
<!-- Popup Triggers -->
<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>
<!-- Basic Modal -->
<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>
<!-- Confirmation Modal -->
<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>
<!-- Form Modal -->
<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>
<!-- Image Modal -->
<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>
<!-- Video Modal -->
<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>
<!-- Fullscreen Modal -->
<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>
<!-- Tooltip Popup -->
<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>
<!-- Notification Popup -->
<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>
<!-- Dropdown Popup -->
<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>
<!-- Context Menu Popup -->
<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 */
.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 */
.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 */
.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 */
.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 */
.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 1rem;
padding: 1.5rem 2rem;
border-top: 1px solid #e5e7eb;
background: #f9fafb;
}
/* Form Styles */
.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);
}
/* Button Styles */
.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 */
.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 */
.video-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Fullscreen Content */
.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 */
.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 */
.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 */
.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 */
.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 */
.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 Theme */
.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;
}
/* Responsive Design */
@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;
}
}
/* Reduced Motion */
@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;
}
}
/* Focus Styles */
.modal-close:focus,
.btn:focus,
.dropdown-item:focus,
.context-item:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Animation Classes */
.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() {
// Modal triggers
document.addEventListener('click', (e) => {
const trigger = e.target.closest('[data-modal]');
if (trigger) {
e.preventDefault();
const modalId = trigger.dataset.modal;
this.openModal(modalId);
}
// Popup triggers
const popupTrigger = e.target.closest('[data-popup]');
if (popupTrigger) {
e.preventDefault();
const popupId = popupTrigger.dataset.popup;
this.openPopup(popupId, popupTrigger);
}
// Close buttons
if (e.target.closest('.modal-close, .modal-cancel')) {
e.preventDefault();
this.closeActiveModal();
}
if (e.target.closest('.notification-close')) {
e.preventDefault();
this.closeActivePopup();
}
// Backdrop click
if (e.target.classList.contains('modal-overlay')) {
this.closeActiveModal();
}
});
// Context menu
document.addEventListener('contextmenu', (e) => {
const contextTrigger = e.target.closest('[data-context]');
if (contextTrigger) {
e.preventDefault();
this.openContextMenu(e, 'context-popup');
}
});
// Close popups on outside click
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) => {
// Escape key
if (e.key === 'Escape') {
if (this.activePopup) {
this.closeActivePopup();
} else if (this.activeModal) {
this.closeActiveModal();
}
}
// Tab navigation within modal
if (e.key === 'Tab' && this.activeModal) {
this.handleTabNavigation(e);
}
});
}
setupAccessibility() {
// Add ARIA attributes
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;
// Store previous focus
modal.previousFocus = document.activeElement;
// Close any active popup
if (this.activePopup) {
this.closeActivePopup();
}
// Add to modal stack
this.modalStack.push(modalId);
this.activeModal = modalId;
// Show modal
modal.element.style.display = 'flex';
modal.element.setAttribute('aria-hidden', 'false');
// Trigger animation
requestAnimationFrame(() => {
modal.element.classList.add('active');
if (options.animation) {
modal.container.classList.add(options.animation);
}
});
// Focus management
setTimeout(() => {
this.focusFirstElement(modal.element);
}, 100);
// Prevent body scroll
document.body.style.overflow = 'hidden';
modal.isOpen = true;
// Dispatch event
this.dispatchEvent('modalOpen', { modalId, modal: modal.element });
}
closeModal(modalId) {
const modal = this.modals.get(modalId);
if (!modal || !modal.isOpen) return;
// Remove from stack
const stackIndex = this.modalStack.indexOf(modalId);
if (stackIndex > -1) {
this.modalStack.splice(stackIndex, 1);
}
// Update active modal
this.activeModal = this.modalStack.length > 0 ? this.modalStack[this.modalStack.length - 1] : null;
// Hide modal
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);
// Restore focus
if (modal.previousFocus) {
modal.previousFocus.focus();
modal.previousFocus = null;
}
// Restore body scroll if no modals are open
if (this.modalStack.length === 0) {
document.body.style.overflow = '';
}
modal.isOpen = false;
// Dispatch event
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;
// Close any active popup
if (this.activePopup) {
this.closeActivePopup();
}
this.activePopup = popupId;
popup.trigger = trigger;
// Position popup
this.positionPopup(popup, trigger, options.position);
// Show popup
popup.element.style.display = 'block';
popup.element.setAttribute('aria-hidden', 'false');
requestAnimationFrame(() => {
popup.element.classList.add('active');
});
popup.isOpen = true;
// Auto-close for notifications
if (popup.element.querySelector('.notification') && options.autoClose !== false) {
setTimeout(() => {
this.closePopup(popupId);
}, options.duration || 5000);
}
// Dispatch event
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;
// Dispatch event
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;
// Close any active popup
if (this.activePopup) {
this.closeActivePopup();
}
this.activePopup = popupId;
// Position at cursor
popup.element.style.position = 'fixed';
popup.element.style.left = `${event.clientX}px`;
popup.element.style.top = `${event.clientY}px`;
// Adjust position if near viewport edges
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`;
}
// Show popup
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;
}
// Adjust for viewport boundaries
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);
}
// Public API
show(id, options = {}) {
if (this.modals.has(id)) {
this.openModal(id, options);
} else if (this.popups.has(id)) {
// For popups, we need a trigger element
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() {
// Close all modals
this.modalStack.forEach(modalId => {
this.closeModal(modalId);
});
// Close active popup
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);
// Position notification
this.positionNotification(notification, options.position);
// Show with animation
requestAnimationFrame(() => {
notification.classList.add('active');
});
// Auto-remove
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>
`;
// Add close functionality
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() {
// Clean up event listeners and close all modals/popups
this.hideAll();
document.body.style.overflow = '';
}
}
// Initialize the modal and popup system
const modalPopupSystem = new ModalPopupComponents();
// Export for use in other scripts
if (typeof module !== 'undefined' && module.exports) {
module.exports = ModalPopupComponents;
} else if (typeof window !== 'undefined') {
window.ModalPopupComponents = ModalPopupComponents;
window.modalPopupSystem = modalPopupSystem;
}Usage Examples
Basic Modal
// Open a modal
modalPopupSystem.show('basic-modal');
// Close a modal
modalPopupSystem.hide('basic-modal');
// Check if modal is open
if (modalPopupSystem.isOpen('basic-modal')) {
console.log('Modal is open');
}Notification System
// Show success notification
modalPopupSystem.showNotification('Operation completed successfully!', 'success');
// Show warning with custom options
modalPopupSystem.showNotification('Please check your input', 'warning', {
title: 'Validation Error',
duration: 3000,
position: 'top-center'
});
// Show error notification
modalPopupSystem.showNotification('Something went wrong', 'error', {
duration: 0 // Don't auto-close
});Event Handling
// Listen for modal events
document.addEventListener('modalOpen', (e) => {
console.log('Modal opened:', e.detail.modalId);
});
document.addEventListener('modalClose', (e) => {
console.log('Modal closed:', e.detail.modalId);
});
// Listen for popup events
document.addEventListener('popupOpen', (e) => {
console.log('Popup opened:', e.detail.popupId);
});Custom Modal Creation
// Create a custom modal programmatically
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);
// Register with the system
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
/* Custom slide animation */
@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
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>