Back to Examples

Modal Overlays Layout

Modal Overlays Layout

The modal overlays pattern provides secondary content presentation that maintains focus on primary content while offering contextual information and actions. It features centered modals with backdrop on desktop and bottom sheets with drag interactions on mobile, creating a sophisticated overlay system with comprehensive focus management and accessibility support.

Pattern Overview

Desktop Layout (≥768px)

  • Centered Modals: Overlay with backdrop blur/darken effect
  • Size Variants: Small (400px), Medium (600px), Large (800px), Full-screen
  • Focus Management: Comprehensive focus trapping and restoration
  • Backdrop Interaction: Click outside to close (configurable)
  • Keyboard Navigation: Escape to close, Tab cycling within modal
  • Modal Stack: Support for nested modals with proper z-index management
  • Portal Rendering: Modals render outside normal DOM flow

Mobile Layout (<768px)

  • Bottom Sheets: Slide up from bottom edge of screen
  • Drag Handle: Visual indicator for drag-to-dismiss gesture
  • Touch Interactions: Drag down to dismiss with momentum physics
  • Safe Area Handling: Respect device safe areas and notches
  • Backdrop: Semi-transparent overlay that dismisses on tap
  • Spring Physics: Natural feel during drag interactions
  • Snap Points: Intelligent dismiss threshold based on drag distance

Key Benefits

  • Contextual Information: Secondary content without losing primary context
  • Platform-Appropriate UX: Desktop modals vs mobile bottom sheets
  • Advanced Focus Management: WCAG AA compliant focus trapping
  • Gesture Support: Natural drag-to-dismiss on mobile devices
  • Modal Stack System: Support for nested modals and complex workflows
  • Portal Architecture: Proper z-index and rendering isolation
  • Spring Physics: Natural, responsive touch interactions
  • Universal Content Types: User profiles, settings, media, forms, confirmations

Content Types Demonstrated

User Profile Modals

  • Avatar Display: Large profile image with status indicators
  • Contact Information: Email, phone, timezone details
  • Quick Actions: Send message, start call, view full profile
  • Status Management: Online, away, offline with visual indicators

Settings Modals

  • Notification Preferences: Push notifications, email summaries
  • Appearance Settings: Dark mode, theme preferences
  • Privacy Controls: Blocked users, data settings
  • Account Management: Profile editing, account deletion

Media Viewer

  • Full-Screen Display: Optimized image viewing experience
  • Dark Background: Focused viewing with minimal distractions
  • Action Controls: Download, share, zoom functionality
  • Keyboard Navigation: Arrow keys for navigation, Escape to close

Confirmation Dialogs

  • Warning Icons: Visual indicators for destructive actions
  • Clear Messaging: Explicit confirmation text and consequences
  • Action Buttons: Primary (destructive) and secondary (cancel) actions
  • Keyboard Support: Enter to confirm, Escape to cancel

Form Modals

  • Channel Creation: Name, description, privacy settings
  • User Invitations: Email input, role selection, message customization
  • Content Reporting: Reason selection, additional details
  • Form Validation: Real-time feedback and error handling

Mobile Menu (Bottom Sheet)

  • Navigation Menu: Channel list, direct messages, settings access
  • Drag Handle: Clear visual indicator for interaction
  • Nested Actions: Trigger other modals from within the menu
  • Touch Optimization: Large touch targets and gesture support

Implementation Architecture

CSS Variables for Maintainability

:root {
  --modal-sm: 25rem;                /* Small modal width */
  --modal-md: 37.5rem;              /* Medium modal width */
  --modal-lg: 50rem;                /* Large modal width */
  --sheet-handle-height: 1.5rem;    /* Bottom sheet handle */
  --backdrop-opacity: 0.5;          /* Backdrop transparency */
  --modal-radius: 0.75rem;          /* Modal border radius */
  --transition-duration: 300ms;     /* Standard animation duration */
  --spring-duration: 400ms;         /* Spring animation duration */
  --spring-easing: cubic-bezier(0.34, 1.56, 0.64, 1); /* Spring easing */
}

Portal Architecture

/* Modal Portal Container */
.modal-portal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1000;
  pointer-events: none;
}

.modal-portal.active {
  pointer-events: auto;
}

Responsive Modal Behavior

/* Desktop: Centered modals */
@media (min-width: 768px) {
  .bottom-sheet {
    display: none;
  }
  
  .modal-container {
    display: flex;
  }
}

/* Mobile: Bottom sheets */
@media (max-width: 767px) {
  .modal-container {
    display: none;
  }
  
  .bottom-sheet {
    display: flex;
  }
}

Bottom Sheet with Drag Support

.bottom-sheet {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: white;
  border-radius: var(--modal-radius) var(--modal-radius) 0 0;
  transform: translateY(100%);
  transition: transform var(--spring-duration) var(--spring-easing);
  max-height: 90vh;
  touch-action: none;
}

.bottom-sheet.active {
  transform: translateY(0);
}

.bottom-sheet.dragging {
  transition: none;
}

Modal Size Variants

/* Modal Size Variants */
.modal-sm { width: var(--modal-sm); max-width: 90vw; }
.modal-md { width: var(--modal-md); max-width: 90vw; }
.modal-lg { width: var(--modal-lg); max-width: 90vw; }
.modal-fullscreen { width: 95vw; height: 90vh; }

Advanced State Management

Modal Stack System

// Modal system state
let currentModal = null;
let modalStack = [];
let focusedElementBeforeModal = null;

function openModal(template, size = 'md', data = null) {
  // Store currently focused element
  focusedElementBeforeModal = document.activeElement;
  
  // Add to stack
  currentModal = { template, size, data };
  modalStack.push(currentModal);
  
  // Show modal with proper content
  showModalContent(template, size, data);
}

function closeModal() {
  // Remove from stack
  modalStack.pop();
  currentModal = modalStack.length > 0 ? modalStack[modalStack.length - 1] : null;
  
  // Restore focus if no modals remain
  if (modalStack.length === 0 && focusedElementBeforeModal) {
    focusedElementBeforeModal.focus();
    focusedElementBeforeModal = null;
  }
}

Focus Management System

function trapFocus() {
  const container = window.innerWidth < 768 ? bottomSheet : modalContainer;
  const focusableElements = container.querySelectorAll(focusableSelectors);
  
  if (focusableElements.length === 0) return;
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  
  // Focus first element
  firstElement.focus();
  
  // Trap focus within modal
  container.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          e.preventDefault();
          lastElement.focus();
        }
      } else {
        if (document.activeElement === lastElement) {
          e.preventDefault();
          firstElement.focus();
        }
      }
    }
  });
}

Touch Gesture Handling

function handleTouchStart(e) {
  if (!bottomSheet.classList.contains('active')) return;
  
  isDragging = true;
  dragStartY = e.touches[0].clientY;
  sheetStartY = bottomSheet.getBoundingClientRect().top;
  bottomSheet.classList.add('dragging');
}

function handleTouchMove(e) {
  if (!isDragging) return;
  
  e.preventDefault();
  const currentY = e.touches[0].clientY;
  const deltaY = currentY - dragStartY;
  
  // Only allow dragging down
  if (deltaY > 0) {
    const newY = Math.max(0, deltaY);
    bottomSheet.style.transform = `translateY(${newY}px)`;
  }
}

function handleTouchEnd(e) {
  if (!isDragging) return;
  
  isDragging = false;
  bottomSheet.classList.remove('dragging');
  
  const currentY = e.changedTouches[0].clientY;
  const deltaY = currentY - dragStartY;
  const threshold = window.innerHeight * 0.3;
  
  if (deltaY > threshold) {
    closeModal();
  } else {
    bottomSheet.style.transform = '';
  }
}

Template System

Dynamic Content Generation

const modalTemplates = {
  userProfile: (userData) => `
    

User Profile

${userData.initials}

${userData.name}

${userData.role}

`, settings: () => ``, mediaViewer: (imageSrc) => ``, confirmation: (action) => `` };

Template Usage

// Open user profile modal
document.querySelectorAll('.user-profile-trigger').forEach(btn => {
  btn.addEventListener('click', () => {
    const user = btn.getAttribute('data-user');
    if (user && userData[user]) {
      openModal('userProfile', 'md', userData[user]);
    }
  });
});

// Open settings modal
document.querySelectorAll('.settings-trigger').forEach(btn => {
  btn.addEventListener('click', () => {
    openModal('settings', 'md');
  });
});

// Open media viewer
document.querySelectorAll('.media-viewer-trigger').forEach(btn => {
  btn.addEventListener('click', () => {
    const img = btn.querySelector('img');
    const fullSrc = img.getAttribute('data-full-src') || img.src;
    openModal('mediaViewer', 'fullscreen', fullSrc);
  });
});

Accessibility Implementation

WCAG AA Compliance

  • Focus Management: Comprehensive focus trapping and restoration
  • Keyboard Navigation: Tab cycling, Escape to close, Enter to activate
  • Screen Reader Support: Proper ARIA labels and announcements
  • Color Independence: Don't rely solely on color for state indication
  • Touch Targets: Minimum 44px touch targets for mobile accessibility
  • Reduced Motion: Respect prefers-reduced-motion setting

Focus Trap Implementation

const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';

function trapFocus() {
  const container = window.innerWidth < 768 ? bottomSheet : modalContainer;
  const focusableElements = container.querySelectorAll(focusableSelectors);
  
  if (focusableElements.length === 0) return;
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  
  // Focus first element
  firstElement.focus();
  
  // Handle Tab key cycling
  container.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
  });
}

Screen Reader Announcements


Performance Optimization

Animation Performance

  • Hardware Acceleration: Use transform and opacity for all animations
  • Spring Physics: Natural feel with cubic-bezier timing functions
  • Reduced Motion: Respect user preferences for reduced motion
  • Frame Rate: 60fps animations with optimized timing
  • Layer Optimization: Proper z-index management for smooth transitions

Memory Management

  • Event Cleanup: Remove event listeners when modals are closed
  • Content Cleanup: Clear modal content after animations complete
  • Stack Management: Efficient modal stack with proper cleanup
  • Touch Event Optimization: Passive event listeners where appropriate

Safe Area Support

/* Safe Area Support for Mobile */
@supports (padding-bottom: env(safe-area-inset-bottom)) {
  .bottom-sheet {
    padding-bottom: env(safe-area-inset-bottom);
  }
}

Customization Guide

Adjusting Modal Sizes

Simply update the CSS variables:

:root {
  --modal-sm: 20rem;   /* Smaller small modals */
  --modal-md: 40rem;   /* Larger medium modals */
  --modal-lg: 60rem;   /* Larger large modals */
}

Custom Animation Timing

Modify animation variables:

:root {
  --transition-duration: 250ms;  /* Faster transitions */
  --spring-duration: 350ms;      /* Faster spring animations */
  --spring-easing: cubic-bezier(0.25, 1.5, 0.5, 1); /* Different spring curve */
}

Adding Custom Modal Types

Extend the template system:

const modalTemplates = {
  // Existing templates...
  
  customModal: (data) => `
    

${data.title}

${data.content}

` }; // Usage openModal('customModal', 'md', { title: 'Custom Title', content: 'Custom content' });

Browser Support and Fallbacks

Feature Modern Browsers Fallback
CSS Transforms Hardware-accelerated animations Opacity transitions
Touch Events Native drag gestures Click-based interactions
Safe Area Insets Proper notch handling Fixed padding values
CSS Variables Dynamic theming Fixed values in CSS
Backdrop Filter Blur effects Solid color backdrop

Modal Overlays Highlights

  • Platform-Appropriate UX: Desktop modals vs mobile bottom sheets
  • Advanced Focus Management: WCAG AA compliant focus trapping
  • Gesture Support: Natural drag-to-dismiss on mobile devices
  • Modal Stack System: Support for nested modals and complex workflows
  • Portal Architecture: Proper z-index and rendering isolation
  • Spring Physics: Natural, responsive touch interactions
  • Template System: Flexible content generation with data binding
  • Universal Content Types: Profiles, settings, media, forms, confirmations

When to Choose Modal Overlays

Ideal Use Cases

  • • Secondary content that doesn't require navigation
  • • User profiles and contact information
  • • Settings and preferences
  • • Media viewing and galleries
  • • Form dialogs and confirmations
  • • Contextual actions and quick tasks
  • • Mobile menu overlays

Consider Alternatives When

  • • Primary navigation is needed
  • • Complex multi-step workflows
  • • Content requires persistent visibility
  • • Deep hierarchical navigation
  • • Multiple simultaneous content areas
  • • Desktop-first applications

The modal overlays pattern excels in applications requiring contextual secondary content presentation without losing focus on primary tasks. Its sophisticated responsive behavior automatically adapts between desktop modals and mobile bottom sheets, while the comprehensive focus management and accessibility support ensure excellent usability across all devices and user capabilities. The template system and modal stack architecture make it ideal for complex applications with varied content types and nested interaction flows.