MDL001

Modal Dialog

A customizable, accessible modal with smooth animations

UI Components
Font Awesome Included: This snippet uses Font Awesome icons. The required stylesheet is automatically included at the top of the code block. If you are going to use this snippet in your project, add the stylesheet to your <head>.

Live Preview

Code

HTML
<!-- 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>
CSS
/* 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);
  }
}
JavaScript
/**
 * 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
    }
  });
});
Usage Examples
// 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();