Dialog
A centered modal on the raised surface, with a close button and a fade-and-scale entrance.
Dialog is a centered modal for work that needs the whole of someone's attention, like a rename, a short form, or a detail that does not belong on the page. It sits on the raised surface over a scrim, takes a corner close button by default, and handles focus, Escape, and the page behind it for you. It is built on the same Radix dialog as Sheet, which docks to an edge instead of the center.
Usage
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
<Dialog>
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>Supporting copy.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>What closes it
A dialog closes from the corner button, from any DialogClose, with Escape, and with a click
on the scrim. The corner button is on by default, and showCloseButton={false} removes it
when the footer already holds the way out. Escape and the scrim still close it, so a dialog
that must not be dismissed that way is an Alert dialog.
The corner button is 36px square, and 44px on a touch screen, and it fades in from a resting
opacity on hover. DialogHeader keeps its text clear of it, so a long title wraps before the
button. The DialogFooter stacks its buttons with the
primary action on top on a narrow screen, and lines them up on the right from sm up.
Name it with a title
DialogTitle names the dialog and DialogDescription describes it, and each is wired up
only when it is rendered. A dialog with no DialogTitle has no accessible name, and this
version of Radix does not warn about it. If a design has no visible title, render one inside
a visually hidden wrapper such as sr-only.
Without a trigger
open and onOpenChange control a dialog from your own state, and then a DialogTrigger is
optional. Radix returns focus only to a DialogTrigger, so a dialog opened from state loses
focus to the page on close unless you send it somewhere. Do that in onCloseAutoFocus on
DialogContent.
<DialogContent
onCloseAutoFocus={(event) => {
event.preventDefault();
openerRef.current?.focus();
}}
>Size and long content
DialogContent is 448px wide and keeps 16px from each edge of a narrower screen. It is at
most the screen's height less 32px, and content taller than that scrolls inside it, the
corner button with it. Pass a className such as sm:max-w-lg for a wider dialog, or use
Sheet for a long form.
Props
Dialog takes the props of the Radix Dialog it is built on, including open, defaultOpen,
onOpenChange, and modal. DialogContent adds one.
| Prop | Type | Default | What it does |
|---|---|---|---|
showCloseButton | boolean | true | Shows the corner close button, labelled "Close" for screen readers. |
DialogOverlay and DialogPortal are exported for building a custom content layout. With
modal={false} there is no scrim, the page stays usable, and focus is not trapped.
Accessibility
DialogContenthasrole="dialog".aria-labelledbypoints atDialogTitleandaria-describedbyatDialogDescription, each only while it is rendered.- Focus moves into the dialog on open, to the first focusable element in the content, and Tab and Shift+Tab loop inside it. The corner button comes last in that order.
- On close, focus returns to the
DialogTrigger. Without one, focus is not returned unless you do it inonCloseAutoFocus. - Escape closes it, and so does a click on the scrim, except a right click.
- While it is open the page behind is hidden from assistive technology with
aria-hidden, does not take clicks, and does not scroll. - The corner button is a native button named "Close" and shows a 2px focus ring on
:focus-visible. It is 36px square, and 44px on a coarse pointer such as a touch screen, at any width. - The enter and exit are a fade on the scrim and a fade-and-scale on the content over the base duration, and reduced motion drops both so the dialog appears at once.
- The kit adds no rules for forced colors to the scrim or the content, and has not been checked in Windows high contrast. The content keeps its border, which the system paints.
When to use something else
- Alert dialog when the person must answer before doing anything else. It has no close button, and Escape and a click outside do not dismiss it.
- Confirm for a yes or no question with a verb, a trigger, and a pending state while the action runs.
- Sheet for a panel that docks to an edge and holds a long form or a detail beside the page.
- Drawer on phones, for a bottom sheet that rests at heights you choose. A centered dialog is the better fit on a wide screen.
Install
shadcn add @gesso/dialog