Building Accessible Dialogs
Use Headless UI's Dialog component for modals with built-in focus trapping and escape key handling, styled entirely with Tailwind.
What Makes a Dialog Accessible?
An accessible dialog (modal) must satisfy several requirements: it must receive keyboard focus when opened, trap focus within itself so users cannot Tab outside, be dismissable with the Escape key, apply correct ARIA attributes (role='dialog', aria-modal='true'), and return focus to the trigger element when closed. These requirements are complex to implement correctly. Headless UI's Dialog component handles all of them automatically.
Basic Dialog Structure
The Headless UI Dialog composes three key elements: Dialog (root, handles ARIA and focus), Dialog.Panel (the visible modal box), and optionally Dialog.Title and Dialog.Description (for semantic labeling). The open prop controls visibility, and onClose fires when the user presses Escape or clicks outside the panel — you decide what action to take (typically setting open state to false).
import { Dialog } from '@headlessui/react';
import { useState } from 'react';
function AlertDialog() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Open Dialog</button>
<Dialog open={open} onClose={() => setOpen(false)}>
<Dialog.Panel>
<Dialog.Title>Alert</Dialog.Title>
<Dialog.Description>This is an important message.</Dialog.Description>
<button onClick={() => setOpen(false)}>Close</button>
</Dialog.Panel>
</Dialog>
</>
);
}All lessons in this course
- Introduction to Headless UI
- Styling Headless Menu and Dropdown
- Building Accessible Dialogs
- Transitions With Headless UI