A customizable, accessible modal with smooth animations
<head>
.<!-- Font Awesome Icons; Make sure to add it in the <head> tag -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Modal Trigger Button -->
<button id="open-modal-btn" class="modal-trigger-btn">Open Modal</button>
<!-- Modal Overlay and Container -->
<div class="modal-overlay" id="modal-overlay">
<div class="modal-container" role="dialog" aria-labelledby="modal-title" aria-modal="true">
<div class="modal-header">
<h3 id="modal-title">Modal Title</h3>
<button class="modal-close-btn" aria-label="Close modal">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-content">
<p>This is a customizable modal dialog with smooth animations and accessibility features.</p>
<p>It can be styled to match your application's design system.</p>
</div>
<div class="modal-footer">
<button class="modal-cancel-btn">Cancel</button>
<button class="modal-confirm-btn">Confirm</button>
</div>
</div>
</div>
/* Modal Trigger Button */
.modal-trigger-btn {
padding: 12px 24px;
background: linear-gradient(135deg, #6a11cb, #2575fc);
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.modal-trigger-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
}
.modal-trigger-btn:active {
transform: translateY(-1px);
}
/* Modal Overlay */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
backdrop-filter: blur(5px);
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
/* Modal Container */
.modal-container {
background-color: white;
width: 90%;
max-width: 500px;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
transform: translateY(20px) scale(0.95);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
overflow: hidden;
}
.modal-overlay.active .modal-container {
transform: translateY(0) scale(1);
opacity: 1;
}
/* Modal Header */
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid #eee;
background-color: #f8f9fa;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.modal-close-btn {
background: none;
border: none;
font-size: 16px;
color: #666;
cursor: pointer;
transition: color 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 50%;
}
.modal-close-btn:hover {
color: #f44336;
background-color: rgba(244, 67, 54, 0.1);
}
/* Modal Content */
.modal-content {
padding: 24px;
max-height: 60vh;
overflow-y: auto;
}
.modal-content p {
margin: 0 0 16px;
line-height: 1.6;
color: #555;
}
.modal-content p:last-child {
margin-bottom: 0;
}
/* Modal Footer */
.modal-footer {
display: flex;
justify-content: flex-end;
padding: 16px 24px;
border-top: 1px solid #eee;
gap: 12px;
}
.modal-cancel-btn,
.modal-confirm-btn {
padding: 10px 16px;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.modal-cancel-btn {
background-color: #f0f0f0;
color: #555;
border: 1px solid #ddd;
}
.modal-cancel-btn:hover {
background-color: #e0e0e0;
}
.modal-confirm-btn {
background: linear-gradient(135deg, #6a11cb, #2575fc);
color: white;
border: none;
}
.modal-confirm-btn:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
/* Animation Keyframes */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
transform: translateY(20px);
}
to {
transform: translateY(0);
}
}
/* Prevent scrolling on body when modal is open */
body.modal-open {
overflow: hidden;
}
/* Responsive styles */
@media (max-width: 768px) {
.modal-container {
width: 95%;
}
.modal-header,
.modal-content,
.modal-footer {
padding: 16px;
}
}
/* Dark mode styles */
@media (prefers-color-scheme: dark) {
.modal-container {
background-color: #2a2a2a;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
.modal-header {
background-color: #333;
border-bottom-color: #444;
}
.modal-header h3 {
color: #eee;
}
.modal-close-btn {
color: #aaa;
}
.modal-close-btn:hover {
color: #ff6b6b;
background-color: rgba(255, 107, 107, 0.1);
}
.modal-content p {
color: #bbb;
}
.modal-footer {
border-top-color: #444;
}
.modal-cancel-btn {
background-color: #444;
color: #ddd;
border-color: #555;
}
.modal-cancel-btn:hover {
background-color: #555;
}
.modal-confirm-btn {
background: linear-gradient(135deg, #8a3eff, #3a7bd5);
}
}
/**
* Modal Dialog Component
* A customizable, accessible modal dialog with smooth animations
*/
class Modal {
/**
* Initialize a new Modal instance
* @param {Object} options - Configuration options
*/
constructor(options = {}) {
// Default options
this.options = {
triggerSelector: '#open-modal-btn',
modalSelector: '#modal-overlay',
closeSelector: '.modal-close-btn',
cancelSelector: '.modal-cancel-btn',
confirmSelector: '.modal-confirm-btn',
activeClass: 'active',
bodyClass: 'modal-open',
onOpen: () => {},
onClose: () => {},
onConfirm: () => {},
onCancel: () => {},
...options
};
// DOM Elements
this.trigger = document.querySelector(this.options.triggerSelector);
this.modal = document.querySelector(this.options.modalSelector);
this.closeBtn = this.modal.querySelector(this.options.closeSelector);
this.cancelBtn = this.modal.querySelector(this.options.cancelSelector);
this.confirmBtn = this.modal.querySelector(this.options.confirmSelector);
// Initialize
this.init();
}
/**
* Initialize event listeners
*/
init() {
// Open modal on trigger click
if (this.trigger) {
this.trigger.addEventListener('click', this.open.bind(this));
}
// Close modal when close button is clicked
if (this.closeBtn) {
this.closeBtn.addEventListener('click', this.close.bind(this));
}
// Handle cancel button click
if (this.cancelBtn) {
this.cancelBtn.addEventListener('click', this.handleCancel.bind(this));
}
// Handle confirm button click
if (this.confirmBtn) {
this.confirmBtn.addEventListener('click', this.handleConfirm.bind(this));
}
// Close modal when clicking on overlay
this.modal.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.close();
}
});
// Close modal with Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen()) {
this.close();
}
});
}
/**
* Open the modal
* @returns {Modal} - Returns the modal instance for chaining
*/
open() {
this.modal.classList.add(this.options.activeClass);
document.body.classList.add(this.options.bodyClass);
// Set focus to the first focusable element in the modal
setTimeout(() => {
const focusableElements = this.modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusableElements.length > 0) {
focusableElements[0].focus();
}
}, 50);
this.options.onOpen();
return this;
}
/**
* Close the modal
* @returns {Modal} - Returns the modal instance for chaining
*/
close() {
this.modal.classList.remove(this.options.activeClass);
document.body.classList.remove(this.options.bodyClass);
// Return focus to the trigger
if (this.trigger) {
this.trigger.focus();
}
this.options.onClose();
return this;
}
/**
* Handle confirm button click
*/
handleConfirm() {
this.options.onConfirm();
this.close();
}
/**
* Handle cancel button click
*/
handleCancel() {
this.options.onCancel();
this.close();
}
/**
* Check if the modal is currently open
* @returns {boolean} - True if modal is open
*/
isOpen() {
return this.modal.classList.contains(this.options.activeClass);
}
/**
* Set the modal title
* @param {string} title - The new title
* @returns {Modal} - Returns the modal instance for chaining
*/
setTitle(title) {
const titleElement = this.modal.querySelector('#modal-title');
if (titleElement) {
titleElement.textContent = title;
}
return this;
}
/**
* Set the modal content
* @param {string} content - HTML content
* @returns {Modal} - Returns the modal instance for chaining
*/
setContent(content) {
const contentElement = this.modal.querySelector('.modal-content');
if (contentElement) {
contentElement.innerHTML = content;
}
return this;
}
}
// Initialize the modal when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', function() {
const modal = new Modal({
onConfirm: () => {
console.log('Modal confirmed!');
// Add your custom confirm logic here
},
onCancel: () => {
console.log('Modal cancelled!');
// Add your custom cancel logic here
}
});
});
// Basic initialization
const basicModal = new Modal();
// Initialization with custom options
const customModal = new Modal({
triggerSelector: '#custom-trigger',
modalSelector: '#custom-modal',
onOpen: () => {
console.log('Modal opened!');
},
onClose: () => {
console.log('Modal closed!');
}
});
// Programmatically open modal
document.querySelector('#programmatic-trigger').addEventListener('click', () => {
basicModal.open();
});
// Creating a confirmation dialog
const confirmModal = new Modal({
onConfirm: () => {
// Perform action when user confirms
saveChanges();
},
onCancel: () => {
// Handle cancellation
resetForm();
}
});
// Set dynamic content
confirmModal.setTitle('Save Changes?')
.setContent('Are you sure you want to save these changes?
')
.open();