What is Modal dialog?
Modal dialog: A modal dialog is a window layered over the page that blocks interaction with everything behind it until the user completes or dismisses it.
A modal dialog interrupts the current task to ask for a decision or a short, focused input: confirm a deletion, rename a file, invite a teammate. While it is open, the page behind is dimmed and inert, so the user cannot click, tab or scroll into it. That strength is also the cost. A modal takes over the screen, so use it only when the task truly needs the user's full attention before anything else happens.
When to use it
- Confirming a destructive or irreversible action. Use the alert dialog variant, which requires an explicit choice.
- Short forms of one to four fields that belong to the current page, such as renaming or creating an item.
- Not for long forms, content people want to compare with the page behind, or messages that could be inline. A side sheet or a separate page is usually better.
- Never on page load for marketing or cookie messages that block content.
Accessibility
The WAI-ARIA dialog (modal) pattern sets out the behavior. The container has role="dialog" and aria-modal="true", is named by its title through aria-labelledby, and can point to a description with aria-describedby. Use role="alertdialog" for confirmations that interrupt with an urgent message.
- On open, move focus inside the dialog: to the first field, or to the least destructive button in a confirmation.
- Tab and Shift+Tab cycle within the dialog; focus never escapes to the page behind.
- Escape closes the dialog.
- On close, return focus to the element that opened it.
- Content behind the dialog is inert and hidden from assistive technology.
The native dialog element opened with showModal() provides inertness and Escape handling for free. Headless libraries like Base UI handle the rest, including focus return and scroll locking.
How to build one in React
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export function RenameProject() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Rename</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>The URL will not change.</DialogDescription>
</DialogHeader>
<input aria-label="Project name" defaultValue="Acme web" className="h-9 rounded-lg border border-border px-3" />
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Base UI's render prop lets the trigger and close controls render as your own Button component while keeping the dialog's behavior and ARIA attributes.
Common mistakes
- Opening a modal from a modal. Replace the content or use a stepper inside one dialog.
- Closing on backdrop click while a form has unsaved input.
- Layout shift when scroll locking removes the scrollbar. Reserve it with
scrollbar-gutter: stable. - Dialogs taller than the viewport with no internal scroll. Let the body scroll and keep the title and actions visible.
- Vague button labels like OK. Name the primary button after the action, such as Delete project.
MiniDev's Dialog and AlertDialog are in the overlays category.
DialoguiShadcn compatible modal dialog on Base UI with a blurred backdrop, enter and exit animation, close button, header, title, description and a footer bar.npx shadcn@latest add ui.minidev.pro/r/dialog.jsonAlert DialoguiShadcn style alert dialog on Base UI for confirmations that need an explicit answer. Modal backdrop, focus trap, title, description and an actions row.npx shadcn@latest add ui.minidev.pro/r/alert-dialog.json