MDL001

Modal Dialog

A customizable, accessible modal with smooth animations

UI Components
Snippet Updated: This modal snippet now uses a custom SVG icon for the close button instead of Font Awesome. Enjoy!

Live Preview

Code

HTML
<!-- 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">
        <svg viewBox="0 0 24 24" fill="none" width="20" height="20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
          <g>
            <path d="M16 16L12 12M12 12L8 8M12 12L16 8M12 12L8 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
          </g>
        </svg>
      </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();