Dialog
StableA window overlaid on the page that focuses attention on one task and blocks everything else until it is finished or dismissed.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/dialog.jsonUsage
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Edit profile</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Changes are visible to everyone in your workspace.</DialogDescription>
</DialogHeader>
{/* … */}
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
<Button>Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>DialogContent renders the portal, the scrim and the popup for you. Triggers and close buttons take a render prop, so the element that opens the dialog is a real prfct Button rather than a wrapper around one.
Examples
Form
The most common dialog: a short form that edits one thing. Focus moves to the first field when the dialog opens and returns to the trigger when it closes. Keep it to a handful of fields — anything longer deserves a page or a Sheet. Wrap the fields and the footer in one <form> so Enter submits.
Scrollable content
When the content is taller than the viewport, scroll only the body so the title and the actions stay visible. The dialog itself never grows beyond the viewport minus a 1rem margin on each side.
Controlled
Dialogs opened from a menu item, a keyboard shortcut or after an async step have no trigger of their own. Render the dialog outside the menu and control it with open and onOpenChange.
const [open, setOpen] = React.useState(false)
<DropdownMenuItem onClick={() => setOpen(true)}>Rename…</DropdownMenuItem>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>{/* … */}</DialogContent>
</Dialog>Footer
DialogFooter is a tinted strip anchored to the bottom edge. Put actions on the right, secondary context on the left, and use showCloseButton when a dialog only needs a way out.
Width
The default maximum width is max-w-lg (32rem), right for a form. Widen it with a max-w-* class for reference content such as a shortcut list, and keep the side margin: the dialog is always calc(100% - 2rem) wide at most.
Guidelines
When to use
- To collect a small amount of input without leaving the page: rename, invite, edit a few fields.
- To show information that must be acknowledged before continuing.
- To confirm a task started from the current page, with enough context to decide.
When not to use
- To confirm a destructive or irreversible action — use an Alert Dialog, which can't be dismissed by clicking outside.
- For long forms, multi-step flows or anything people need to compare against the page — use a Sheet or a full page.
- For a success message after an action — use a Toast. Interrupting people to say that things went fine is a tax on every task.
- For contextual controls tied to one element — use a Popover.
Choosing an overlay
Every overlay takes some of the page away to focus on one task — the heavier the overlay, the more it hides. Pick the lightest one that does the job.
Writing
Title the dialog with what it does, not with a question you then repeat: Edit profile, Invite teammates. Name the primary button with the outcome, and repeat the key noun if it helps: Save changes, Send 3 invites. The description is optional but usually worth one sentence of context.
One at a time
Never stack a dialog on top of another dialog. If a task needs a second step, replace the content of the first, or move the whole flow to a page.
Accessibility
The dialog follows the WAI-ARIA dialog pattern. Base UI renders the popup with role="dialog" and aria-modal, labels it with DialogTitle and describes it with DialogDescription.
| Key | Behavior |
|---|---|
EnterSpace | On the trigger: opens the dialog and moves focus inside it. |
Tab | Moves focus to the next focusable element. Focus is trapped inside the dialog. |
ShiftTab | Moves focus to the previous focusable element, wrapping at the start. |
Esc | Closes the dialog and returns focus to the element that opened it. |
- Always include a title.
DialogTitlegives the dialog its accessible name. If the design has no visible title, keep it and addclassName="sr-only". - Focus. Focus moves to the first focusable element on open — or to the popup itself when opened by touch, so the keyboard doesn't jump up. Change it with
initialFocusandfinalFocusonDialogContent. - Everything else is inert. While a modal dialog is open, page scroll is locked and content outside can't be reached with the pointer, keyboard or screen reader.
- A way out. Keep the close button (
showCloseButton, on by default) or aDialogClosebutton in the footer. Touch screen reader users can't press Esc. - Motion. The dialog fades and scales in over 320ms with the
entereasing and leaves faster than it arrived. With reduced motion, it appears instantly.
API reference
Dialog
The root. Doesn't render an element. Accepts every prop of Base UI Dialog.Root.
DialogTrigger
Opens the dialog. Renders a <button>; use render to render a prfct Button.
DialogContent
Renders the portal, scrim and popup.
DialogHeader, DialogTitle, DialogDescription
DialogHeader stacks the title and description and reserves room for the close button. DialogTitle renders an <h2> and names the dialog; DialogDescription renders a <p> and describes it.
DialogFooter
A tinted strip, flush with the dialog's bottom edge. Stacks actions on small screens, right-aligns them from sm up.
DialogClose
Closes the dialog. Renders a <button>; use render to render a prfct Button.