Back

Common Accessibility Issues with Modals (and How to Fix Them)

Common Accessibility Issues with Modals (and How to Fix Them)

Modal dialogs are everywhere in modern web applications, but they’re also one of the most common sources of accessibility failures. A modal is a dialog box that appears over the main content and requires user interaction before continuing. When implemented poorly, modals can trap keyboard users, confuse screen readers, and create frustrating experiences for anyone relying on assistive technologies. Let’s examine the most frequent accessibility problems with modals and their practical solutions.

Key Takeaways

  • Focus management is critical: move focus to the modal when opened and return it to the trigger element when closed
  • Use proper ARIA attributes including role=“dialog”, aria-modal=“true”, and accessible labels
  • Implement complete keyboard navigation with Tab cycling and Escape key support
  • Make background content completely inert while the modal is open
  • Test with real assistive technologies, not just automated tools

The Critical Role of Focus Management

The most severe accessibility issue with modals is broken focus management. When a modal opens, focus should immediately move to the modal itself—typically the first interactive element or the modal container. When it closes, focus must return to the element that triggered it.

Common mistake: Focus remains on the background content, allowing users to tab through elements behind the modal.

Solution: Implement proper focus management:

// When opening the modal
const triggerElement = document.activeElement;
modal.showModal(); // or modal.focus() for custom implementations

// When closing the modal
modal.close();
triggerElement.focus();

For React applications using focus-trap-react:

import FocusTrap from 'focus-trap-react';

function Modal({ isOpen, onClose, children }) {
  return (
    <FocusTrap active={isOpen}>
      <div role="dialog" aria-modal="true">
        {children}
      </div>
    </FocusTrap>
  );
}

Missing or Incorrect ARIA Attributes

Screen readers need explicit information about modal dialogs to announce them properly. Missing or misused ARIA attributes leave users guessing about the modal’s purpose and state.

Common mistakes:

  • No role="dialog" or role="alertdialog"
  • Missing aria-modal="true"
  • No accessible name via aria-label or aria-labelledby

Solution: Use appropriate ARIA attributes:

<!-- Using native dialog element (recommended) -->
<dialog aria-labelledby="modal-title" aria-describedby="modal-desc">
  <h2 id="modal-title">Delete Confirmation</h2>
  <p id="modal-desc">This action cannot be undone.</p>
</dialog>

<!-- Using div with ARIA -->
<div role="dialog" 
     aria-modal="true" 
     aria-labelledby="modal-title"
     aria-describedby="modal-desc">
  <!-- Modal content -->
</div>

Use role="alertdialog" for critical alerts requiring immediate user response, as it triggers more assertive screen reader announcements.

Broken Keyboard Navigation

Every modal must be fully keyboard accessible. Users should navigate through interactive elements with Tab/Shift+Tab and close the modal with Escape.

Common mistakes:

  • No Escape key support
  • Focus can escape the modal (no focus trap)
  • Tab order doesn’t match visual layout

Solution: Implement a complete focus trap:

function trapFocus(modalElement) {
  const focusableElements = modalElement.querySelectorAll(
    'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  
  modalElement.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      closeModal();
      return;
    }
    
    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();
      }
    }
  });
  
  firstElement.focus();
}

Background Content Remains Interactive

When a modal is open, background content should be completely inert. Users shouldn’t be able to interact with anything behind the modal.

Common mistake: Background remains scrollable or interactive through keyboard navigation.

Solution: Make background content inert:

// When opening modal
document.body.style.overflow = 'hidden';
document.querySelector('main').setAttribute('aria-hidden', 'true');
document.querySelector('main').setAttribute('inert', ''); // Modern approach

// When closing modal
document.body.style.overflow = '';
document.querySelector('main').removeAttribute('aria-hidden');
document.querySelector('main').removeAttribute('inert');

Testing Your Modal Accessibility

Automated tools catch some issues, but manual testing remains essential:

  1. Keyboard Testing:

    • Open the modal and verify focus moves to it
    • Tab through all interactive elements
    • Ensure Tab cycles within the modal
    • Press Escape to close
    • Verify focus returns to trigger element
  2. Screen Reader Testing:

    • Test with NVDA (Windows) or VoiceOver (macOS)
    • Verify the modal role is announced
    • Check that title and description are read
    • Ensure background content is not reachable
  3. Visual Testing:

    • Verify focus indicators are visible
    • Check color contrast (WCAG AA requires 4.5:1 for normal text, 3:1 for large text and UI components)
    • Ensure close buttons are clearly labeled

Best Practices for Implementation

Use the native <dialog> element when possible. It provides built-in focus management and ARIA semantics:

const dialog = document.querySelector('dialog');
dialog.showModal(); // Opens with proper focus trap
dialog.close();     // Closes and returns focus

For custom implementations, follow this checklist:

  • Set role="dialog" and aria-modal="true"
  • Provide an accessible name via aria-labelledby or aria-label
  • Implement complete keyboard support (Tab, Shift+Tab, Escape)
  • Create a robust focus trap
  • Disable background scrolling and interaction
  • Return focus to trigger element on close
  • Include visible focus indicators
  • Test with actual assistive technologies

Conclusion

Accessible modals aren’t just about compliance—they create better experiences for all users. By addressing these common issues, you ensure your modals work seamlessly for keyboard users, screen reader users, and everyone in between. Start with semantic HTML, add proper ARIA attributes, implement thorough focus management, and always test with real assistive technologies.

Remember: if your modal doesn’t work without a mouse, it doesn’t work. Fix these issues, and your modals will be truly accessible to everyone.

FAQs

Use role='dialog' for standard modals that contain forms or information. Use role='alertdialog' for critical alerts requiring immediate response, as it causes screen readers to announce the content more assertively and interrupts the user's current task.

CSS can visually obscure content with overlays and prevent pointer events, but it won't stop keyboard navigation. You need JavaScript to add aria-hidden or the inert attribute to truly make background content non-interactive for all users.

Best practice is to focus the first interactive element, typically the close button if it's at the top. However, for modals with critical information, focusing the heading first ensures screen reader users hear the title before any actions.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay