Loading content...
Loading content...
Comprehensive guide to creating accessible modals and dialogs with proper focus management, keyboard trapping, and ARIA patterns.
When a modal opens, focus must move into it immediately. If it doesn't, screen reader users might not realize a dialog has appeared, continuing to browse the underlying page which should be inert.
Without a "focus trap", pressing "Tab" on the last element of the modal might move focus back to the page background (e.g., the URL bar or footer), causing users to lose their place and struggle to close the dialog.
<dialog> element provides built-in accessibility features. Use showModal() to open and close() to close. The dialog automatically traps focus and handles Escape key. Use <form method="dialog"> to close dialog on form submission. Always provide a visible close button.Testing Guide
role="dialog" or role="alertdialog". Set aria-modal="true" to indicate modal behavior. Trap focus within the dialog using JavaScript. Provide aria-labelledby pointing to the dialog title. Use aria-describedby for additional description. Ensure backdrop is properly handled.Testing Guide
The example above demonstrates the ARIA dialog pattern. Focus management and keyboard trapping are implemented.
role="alertdialog" for critical messages requiring immediate attention. They should have a clear, concise message and typically one or two action buttons. The alertdialog role causes screen readers to interrupt current reading to announce the message. Use for confirmations, warnings, or critical errors.Testing Guide
Using generic div or span elements for buttons prevents keyboard users from opening the modal.
Divs are not focusable by default and don't support Enter/Space keys.
<div
onClick={openModal}
className="btn"
>
Open Settings
</div>Buttons are natively focusable and trigger on both click and keypress.
<button
onClick={openModal}
className="btn"
type="button"
>
Open Settings
</button>Failing to move focus to the modal when it opens leaves keyboard users lost.
User focus remains on the trigger button (now behind the backdrop).
const openModal = () => {
setIsOpen(true);
// No focus management
}Programmatically moving focus ensures the user context switches to the modal.
const openModal = () => {
setIsOpen(true);
// Wait for render, then focus
setTimeout(() => {
modalRef.current?.focus();
}, 0);
}