FE Master

Build Your Own Modal Dialog System

DifficultyMedium
Estimated time: 2-3 hours
0 done

Description

# Build Your Own Modal Dialog System Modals are everywhere in modern web applications, but creating accessible, flexible, and performant modals is more challenging than it seems. This challenge will ...

Requirements

  • **Programmatic Control**: Open and close modals via JavaScript
  • **Focus Management**: Trap focus within the modal when open
  • **Accessibility**: Full keyboard navigation and screen reader support
  • **Backdrop Click**: Close modal when clicking outside
  • **Multiple Modals**: Support stacking multiple modals
  • **Animations**: Smooth open/close animations
  • **Scroll Lock**: Prevent background scrolling when modal is open
  • **Return Focus**: Return focus to trigger element on close

Overview

Modals are everywhere in modern web applications, but creating accessible, flexible, and performant modals is more challenging than it seems. This challenge will guide you through building a production-ready modal system that handles focus management, accessibility, and animations.

Getting Started

## Step Zero: Set Up Your Environment - **Create HTML Structure**: Build a basic modal structure - **Add Styling**: Create attractive modal designs with animations

Implementation Guide

Build Your Own Modal Dialog System

Modals are everywhere in modern web applications, but creating accessible, flexible, and performant modals is more challenging than it seems. This challenge will guide you through building a production-ready modal system that handles focus management, accessibility, and animations.

The Challenge - Building a Modal Dialog System

The functional requirements for your modal system include:

  • Programmatic Control: Open and close modals via JavaScript
  • Focus Management: Trap focus within the modal when open
  • Accessibility: Full keyboard navigation and screen reader support
  • Backdrop Click: Close modal when clicking outside
  • Multiple Modals: Support stacking multiple modals
  • Animations: Smooth open/close animations
  • Scroll Lock: Prevent background scrolling when modal is open
  • Return Focus: Return focus to trigger element on close

Step Zero: Set Up Your Environment

  • Create HTML Structure: Build a basic modal structure
  • Add Styling: Create attractive modal designs with animations

Step One: Create the Modal Class

Build the foundation of your modal system.

Example:

class Modal {
    constructor(options = {}) {
        this.isOpen = false;
        this.triggerElement = null;
        this.options = {
            closeOnBackdrop: true,
            closeOnEscape: true,
            showCloseButton: true,
            ...options
        };
        
        this.modal = null;
        this.backdrop = null;
    }
    
    create(content) {
        // Create modal structure
        this.modal = document.createElement('div');
        this.modal.className = 'modal';
        this.modal.setAttribute('role', 'dialog');
        this.modal.setAttribute('aria-modal', 'true');
        
        this.modal.innerHTML = `
            <div class="modal-content">
                <button class="modal-close" aria-label="Close modal">×</button>
                <div class="modal-body">${content}</div>
            </div>
        `;
        
        // Create backdrop
        this.backdrop = document.createElement('div');
        this.backdrop.className = 'modal-backdrop';
        
        return this;
    }
}

Step Two: Implement Open/Close Methods

Add methods to show and hide the modal.

Example:

open(triggerElement) {
    if (this.isOpen) return;
    
    this.triggerElement = triggerElement;
    this.isOpen = true;
    
    // Add to DOM
    document.body.appendChild(this.backdrop);
    document.body.appendChild(this.modal);
    
    // Prevent body scroll
    document.body.style.overflow = 'hidden';
    
    // Trigger animation
    requestAnimationFrame(() => {
        this.backdrop.classList.add('active');
        this.modal.classList.add('active');
    });
    
    // Set up event listeners
    this.attachEvents();
    
    // Focus management
    this.trapFocus();
}

close() {
    if (!this.isOpen) return;
    
    this.isOpen = false;
    
    // Animation out
    this.backdrop.classList.remove('active');
    this.modal.classList.remove('active');
    
    // Remove after animation
    setTimeout(() => {
        this.backdrop.remove();
        this.modal.remove();
        document.body.style.overflow = '';
        
        // Return focus
        if (this.triggerElement) {
            this.triggerElement.focus();
        }
    }, 300); // Match CSS transition time
}

Step Three: Implement Focus Trapping

Keep focus within the modal while it's open.

Example:

trapFocus() {
    const focusableElements = this.modal.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
    // Focus first element
    firstElement?.focus();
    
    // Trap focus within modal
    this.modal.addEventListener('keydown', (e) => {
        if (e.key !== 'Tab') return;
        
        if (e.shiftKey) {
            // Shift + Tab
            if (document.activeElement === firstElement) {
                e.preventDefault();
                lastElement.focus();
            }
        } else {
            // Tab
            if (document.activeElement === lastElement) {
                e.preventDefault();
                firstElement.focus();
            }
        }
    });
}

Step Four: Add Keyboard Support

Handle Escape key and other keyboard interactions.

Example:

attachEvents() {
    // Close button
    const closeBtn = this.modal.querySelector('.modal-close');
    closeBtn?.addEventListener('click', () => this.close());
    
    // Backdrop click
    if (this.options.closeOnBackdrop) {
        this.backdrop.addEventListener('click', () => this.close());
    }
    
    // Escape key
    if (this.options.closeOnEscape) {
        this.escapeHandler = (e) => {
            if (e.key === 'Escape' && this.isOpen) {
                this.close();
            }
        };
        document.addEventListener('keydown', this.escapeHandler);
    }
}

Step Five: Support Multiple Modal Stacking

Allow multiple modals to be open at once.

Example:

class ModalManager {
    constructor() {
        this.modals = [];
    }
    
    open(modal) {
        const zIndex = 1000 + (this.modals.length * 2);
        modal.backdrop.style.zIndex = zIndex;
        modal.modal.style.zIndex = zIndex + 1;
        
        this.modals.push(modal);
        modal.open();
    }
    
    close(modal) {
        const index = this.modals.indexOf(modal);
        if (index > -1) {
            this.modals.splice(index, 1);
            modal.close();
        }
    }
    
    closeTop() {
        const topModal = this.modals[this.modals.length - 1];
        if (topModal) {
            this.close(topModal);
        }
    }
}

Step Six: Add Smooth Animations

Create polished enter/exit animations.

Example CSS:

.modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.7);
    opacity: 0;
    transition: all 0.3s ease;
    max-width: 500px;
    width: 90%;
    background: white;
    border-radius: 8px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}

.modal.active {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
}

.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0);
    transition: background 0.3s ease;
}

.modal-backdrop.active {
    background: rgba(0,0,0,0.5);
}

Step Seven: Implement Scroll Lock

Prevent background scrolling without layout shift.

Example:

lockScroll() {
    const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
    document.body.style.overflow = 'hidden';
    document.body.style.paddingRight = `${scrollbarWidth}px`;
}

unlockScroll() {
    document.body.style.overflow = '';
    document.body.style.paddingRight = '';
}

Step Eight: Add ARIA Attributes

Ensure full screen reader support.

Example:

create(content, title) {
    this.modal.setAttribute('role', 'dialog');
    this.modal.setAttribute('aria-modal', 'true');
    this.modal.setAttribute('aria-labelledby', 'modal-title');
    this.modal.setAttribute('aria-describedby', 'modal-description');
    
    this.modal.innerHTML = `
        <div class="modal-content">
            <h2 id="modal-title">${title}</h2>
            <button class="modal-close" aria-label="Close dialog">×</button>
            <div id="modal-description" class="modal-body">${content}</div>
        </div>
    `;
}

Step Nine: Create Different Modal Types

Build specialized modals for common use cases.

Example:

// Confirmation modal
Modal.confirm = function(message, onConfirm) {
    const modal = new Modal();
    modal.create(`
        <p>${message}</p>
        <div class="modal-actions">
            <button class="btn-cancel">Cancel</button>
            <button class="btn-confirm">Confirm</button>
        </div>
    `);
    
    modal.modal.querySelector('.btn-confirm').addEventListener('click', () => {
        onConfirm();
        modal.close();
    });
    
    return modal;
};

// Alert modal
Modal.alert = function(message) {
    const modal = new Modal();
    modal.create(`
        <p>${message}</p>
        <button class="btn-ok">OK</button>
    `);
    
    return modal;
};

Step Ten: Build a Complete Demo

Create a demo page with:

  • Basic modals
  • Confirmation dialogs
  • Form modals
  • Nested modals
  • Image lightbox modals

Step Eleven: Optional - Advanced Features

  • Add loading states
  • Support for dynamic content loading
  • Custom animations per modal
  • Mobile-optimized full-screen modals
  • Dark mode support

The Final Step: Clean Up and Document

  • Document all options and methods
  • Create comprehensive examples
  • Add accessibility testing checklist
  • Include browser compatibility notes

Help Others by Sharing Your Solutions!

Accessible modals are crucial for inclusive web experiences. Share your implementation to help others build better user interfaces!

CrackIt Dev - Master Frontend Interviews | FE Master | System Design, DSA, JavaScript