Skip to Content
Component ExamplesModals & Dialogs

Overview

Modal dialogs are one of the trickiest components to get right. A visually convincing modal can be completely broken for keyboard and screen reader users if it lacks the dialog role, a focus trap, and proper labeling. When a modal opens, focus must move into it. When it closes, focus must return to the element that triggered it.

WCAG Criteria:

Key requirements:

  • Use role="dialog" (or <dialog> element)
  • Add aria-modal="true" to tell screen readers to ignore content outside
  • Label the dialog with aria-labelledby pointing to the heading inside it
  • Move focus to the first focusable element (or the dialog itself) on open
  • Trap Tab/Shift+Tab within the dialog
  • Close on Escape key
  • Return focus to the trigger element on close

Accessible Dialog vs. Unsemantic Modal Div

Inaccessible
<!-- No dialog role, no focus management, no aria-modal --> <button onclick="document.getElementById('bad-modal').style.display='flex'"> Delete Account </button> <div id="bad-modal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); justify-content:center; align-items:center;"> <div style="background:white; padding:24px; border-radius:8px;"> <h2>Are you sure?</h2> <p>This action cannot be undone.</p> <button onclick="document.getElementById('bad-modal').style.display='none'"> Cancel </button> <button>Delete</button> </div> </div>
Live Preview
Accessible
<button id="modal-trigger" onclick="openModal()">Delete Account</button> <div id="good-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-desc" tabindex="-1" hidden style="position:fixed; inset:0; display:flex; justify-content:center; align-items:center;" > <div style="background:white; padding:24px; border-radius:8px;"> <h2 id="modal-title">Are you sure?</h2> <p id="modal-desc">This action cannot be undone.</p> <button id="modal-cancel" onclick="closeModal()">Cancel</button> <button onclick="closeModal()">Delete</button> </div> </div> <script> function openModal() { var modal = document.getElementById('good-modal'); modal.removeAttribute('hidden'); modal.focus(); // trap focus... } function closeModal() { var modal = document.getElementById('good-modal'); modal.setAttribute('hidden', ''); document.getElementById('modal-trigger').focus(); } </script>
Live Preview

Resources