dialog
A dialog is a window overlaid on either the primary window or another dialog window. Dialogs can be modal (require interaction before returning) or non-modal.
Overview
The dialog role is used to mark up an HTML element as a dialog that temporarily blocks interaction with the main content.
Dialogs are typically used to prompt the user for information, provide a warning message, or require the user to make a decision. When a dialog is active, all other content on the page should be inert (not interactive).
Modal vs Non-Modal
A modal dialog requires user interaction before returning to the main content (use aria-modal="true"). A non-modal dialog allows users to interact with content outside the dialog.
Live Demo
Click the button to see an accessible dialog in action
Required Attributes
aria-labelledbyoraria-labelEvery dialog must have an accessible name. Use aria-labelledby to reference the dialog's title element, or aria-label to provide a direct label.
Supported Attributes
aria-modalIndicates the dialog is modal (true) or non-modal (false)
aria-describedbyReferences element(s) that describe the dialog's content
aria-labelledbyReferences the element that labels the dialog (usually the title)
aria-labelProvides a string label for the dialog
Code Examples
Basic Dialog
<!-- Basic Dialog Example -->
<div role="dialog"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
aria-modal="true">
<h2 id="dialog-title">Delete File</h2>
<p id="dialog-desc">
Are you sure you want to delete this file? This action cannot be undone.
</p>
<div>
<button>Cancel</button>
<button>Delete</button>
</div>
</div>Dialog with Backdrop
<!-- Dialog with Backdrop -->
<div class="dialog-backdrop" aria-hidden="true">
<div role="dialog"
aria-labelledby="dialog-title"
aria-modal="true">
<button aria-label="Close dialog">×</button>
<h2 id="dialog-title">Settings</h2>
<form>
<label for="username">Username</label>
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
</div>
</div>Focus Management
// JavaScript Focus Management
function openDialog(dialogId) {
const dialog = document.getElementById(dialogId);
const focusableElements = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
// Store previously focused element
const previouslyFocused = document.activeElement;
// Show dialog
dialog.hidden = false;
dialog.setAttribute('aria-modal', 'true');
// Focus first element
focusableElements[0].focus();
// Trap focus within dialog
dialog.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
trapFocus(e, focusableElements);
} else if (e.key === 'Escape') {
closeDialog(dialog, previouslyFocused);
}
});
}
function trapFocus(e, focusableElements) {
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}React Component
// React Dialog Component
import { useEffect, useRef } from 'react';
function Dialog({ isOpen, onClose, title, children }) {
const dialogRef = useRef(null);
const previousFocusRef = useRef(null);
useEffect(() => {
if (isOpen) {
// Store previous focus
previousFocusRef.current = document.activeElement;
// Focus first focusable element in dialog
const focusableElements = dialogRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length > 0) {
focusableElements[0].focus();
}
// Prevent body scroll
document.body.style.overflow = 'hidden';
} else {
// Restore focus
if (previousFocusRef.current) {
previousFocusRef.current.focus();
}
// Restore body scroll
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
if (!isOpen) return null;
return (
<div
className="dialog-backdrop"
onClick={onClose}
aria-hidden="true"
>
<div
ref={dialogRef}
role="dialog"
aria-labelledby="dialog-title"
aria-modal="true"
onClick={(e) => e.stopPropagation()}
>
<h2 id="dialog-title">{title}</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
}Keyboard Support
Moves focus to the next focusable element inside the dialog. If focus is on the last element, moves to the first.
Moves focus to the previous focusable element inside the dialog. If focus is on the first element, moves to the last.
Closes the dialog and returns focus to the element that triggered it.
Best Practices
Always provide an accessible name using aria-labelledby or aria-label
Focus must be moved to an element inside the dialog when it opens
Focus must be trapped within the dialog (Tab cycles through focusable elements)
When the dialog closes, focus must return to the element that triggered it
The Escape key should close the dialog
For modal dialogs, use aria-modal="true" and make background content inert
Use aria-describedby to reference the main content description
Don't use role="dialog" without proper focus management
Don't allow focus to escape the dialog when it's modal
Don't forget to disable scrolling on the body when modal is open
Accessibility Notes
Screen Reader Support
When a dialog opens, screen readers will announce the dialog's label and role. Users can navigate through the dialog's content using standard navigation keys. The aria-modal attribute informs assistive technologies that content outside the dialog is inert.
Focus Trap
Focus trapping is critical for modal dialogs. Without it, keyboard and screen reader users can navigate to background content that should be inert. Use JavaScript to trap focus within the dialog and return it to the triggering element when closed.
Initial Focus
When the dialog opens, focus should be placed on the first focusable element, unless there's a more appropriate element (like a confirmation button in a warning dialog). Avoid focusing on the close button as the first element.