Purpose
Modals are dialogs that overlay the main page content. They are used for tasks requiring undivided attention: forms, confirmations, image previews, and content-heavy overlays.
Accessibility Considerations
This component is identical to Dialog in ARIA implementation. See the Dialog component for full documentation. The modal pattern adds:
- Scroll lock (
overflow: hiddenon<body>while open) - Backdrop (semi-transparent overlay) with click-to-dismiss
- Focus management (focus to first element on open; restore on close)
inertattribute on background content (progressive enhancement)
Keyboard Support
ARIA Requirements
role="dialog"orrole="alertdialog"aria-modal="true"aria-labelledbyreferencing the modal heading
Live Example
See the Dialog component for a live interactive example. The Dialog and Modal implementations share the same JavaScript pattern.
Full Implementation Notes
The main.js initDialogs() function handles:
- Opening: shows the overlay, traps focus, locks scroll
- Keyboard: Escape closes; Tab/Shift+Tab cycle within
- Backdrop: click outside the inner panel to close
- Closing: removes the modal, restores scroll, returns focus to trigger
<!-- Modal structure -->
<div
id="my-modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-heading"
hidden
class="modal-overlay"
>
<div class="modal-panel">
<div class="modal-header">
<h2 id="modal-heading">Modal Title</h2>
<button type="button" aria-label="Close modal" data-dialog-close>✕</button>
</div>
<div class="modal-body">
<!-- Modal content here -->
</div>
</div>
</div>
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.5);
display: flex; align-items: center; justify-content: center;
z-index: 200;
}
.modal-panel {
background: #fff;
border-radius: 0.75rem;
max-width: 32rem; width: 90%;
max-height: 90vh; overflow-y: auto;
padding: 2rem;
box-shadow: 0 20px 25px rgba(0,0,0,0.15);
}
@media (prefers-reduced-motion: no-preference) {
.modal-panel {
animation: modal-enter 200ms ease;
}
}
@keyframes modal-enter {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}