Skip to content

Dialog

Stable

A 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.json

Usage

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.

Q3 planning
const [open, setOpen] = React.useState(false)

<DropdownMenuItem onClick={() => setOpen(true)}>Rename…</DropdownMenuItem>

<Dialog open={open} onOpenChange={setOpen}>
  <DialogContent>{/* … */}</DialogContent>
</Dialog>

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.

ComponentBlocks the pageDismiss on outside clickUse it for
DialogYesYesA focused task: a short form, a decision with context.
Alert DialogYesNoConfirming destructive or irreversible actions.
SheetYesYesLonger forms, details and filters that keep the page in view.
DrawerYesYes, or swipeMobile-first bottom sheets with gestures and snap points.
PopoverNoYesSmall, interactive content anchored to a trigger.
Hover CardNoOn pointer leavePreviews of links for sighted pointer users.
TooltipNoOn pointer leaveA short text label for an icon or control.

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.

Invite teammatesThey’ll get an email to join Acme.
Do.The title names the task; the actions name the outcomes.
Are you sure?Please confirm to continue.
Don’t.Generic titles and OK/Cancel force people to reread everything.

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.

KeyBehavior
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. DialogTitle gives the dialog its accessible name. If the design has no visible title, keep it and add className="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 initialFocus and finalFocus on DialogContent.
  • 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 a DialogClose button in the footer. Touch screen reader users can't press Esc.
  • Motion. The dialog fades and scales in over 320ms with the enter easing 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.

PropTypeDefault
open

Whether the dialog is open. Use with onOpenChange to control it.

booleanNo default
defaultOpen

Whether the dialog is open initially, when uncontrolled.

booleanfalse
onOpenChange

Called when the dialog opens or closes. details.reason tells you why: trigger-press, outside-press, escape-key, close-press…

(open: boolean, details) => voidNo default
onOpenChangeComplete

Called after the enter or exit animation finishes.

(open: boolean) => voidNo default
modal

true locks scroll and blocks outside interaction. "trap-focus" only traps focus. false allows interaction with the page.

boolean | "trap-focus"true
disablePointerDismissal

Prevents closing when clicking outside the dialog.

booleanfalse
actionsRef

Imperative handle to close or unmount the dialog.

RefObject<{ close, unmount }>No default

DialogTrigger

Opens the dialog. Renders a <button>; use render to render a prfct Button.

PropTypeDefault
render

Replaces the rendered element while keeping behavior.

ReactElement | (props, state) => ReactElementNo default
nativeButton

Set to false when render produces a non-button element.

booleantrue

DialogContent

Renders the portal, scrim and popup.

PropTypeDefault
showCloseButton

Renders an icon button in the top-right corner that closes the dialog.

booleantrue
initialFocus

Element to focus when the dialog opens. Defaults to the first tabbable element.

boolean | RefObject | (interaction) => HTMLElement | booleanNo default
finalFocus

Element to focus when the dialog closes. Defaults to the trigger.

boolean | RefObject | (interaction) => HTMLElement | booleanNo default
className

Merged with the popup classes. Override the width with max-w-*.

stringNo default

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.

PropTypeDefault
showCloseButton

Appends an outline Close button.

booleanfalse

DialogClose

Closes the dialog. Renders a <button>; use render to render a prfct Button.