Skip to content

ComponentsOverlays

Dialog

A modal window that interrupts the page for a focused task, form or confirmation. Traps focus, locks scroll, restores focus on close, and supports alert, full-screen and controlled modes.

React AriaSource

Installation

pnpm dlx shadcn@latest add @desyne/dialog

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import {
  DialogBody,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
tsx
<DialogTrigger>
  <Button>Open</Button>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Title</DialogTitle>
      <DialogDescription>Description</DialogDescription>
    </DialogHeader>
    {/* content */}
    <DialogFooter>
      <DialogClose>Cancel</DialogClose>
      <Button>Confirm</Button>
    </DialogFooter>
  </DialogContent>
</DialogTrigger>

DialogTrigger connects the first child (any pressable, usually a Button) to the DialogContent after it. No state or refs needed.

When to use

  • Dialog — a short, self-contained task that needs the user's full attention: a form, a confirmation, a detail view.
  • Sheet — longer or secondary content that slides in from an edge and can stay open while users reference the page.
  • Popover — lightweight, non-modal content anchored to a trigger.
  • Toast — feedback that doesn't need a response.

Avoid dialogs that open other dialogs; prefer a multi-step flow inside one dialog.

Anatomy

tsx
<DialogTrigger>
  <Button />                       {/* trigger */}
  <DialogContent>                  {/* overlay + modal + dialog */}
    <DialogIcon />                 {/* optional status icon (alert dialogs) */}
    <DialogHeader>
      <DialogTitle />              {/* accessible name */}
      <DialogDescription />
    </DialogHeader>
    <DialogBody />                 {/* optional scrollable region */}
    <DialogFooter>
      <DialogClose />              {/* button that closes the dialog */}
    </DialogFooter>
  </DialogContent>
</DialogTrigger>
PartRendersNotes
DialogTrigger—Manages open state and wires the trigger to the dialog.
DialogContentModalOverlay › Modal › DialogBackdrop, centered panel and the role="dialog" element. Adds a close icon by default.
DialogHeader<div>Stacks title and description; leaves room for the close icon.
DialogTitleHeading (slot="title")Becomes the dialog's accessible name.
DialogDescription<p>Supporting text.
DialogIcon<div>Tinted circular icon for alert dialogs.
DialogBody<div>Optional. Takes the remaining height and scrolls when content overflows.
DialogFooter<div>Actions. Stacked (primary on top) on mobile, right-aligned on larger screens.
DialogCloseButton slot="close"Any button with slot="close" closes the dialog. Defaults to outline.
DialogCloseIcon<button>The top-right ✕. Rendered automatically unless showCloseButton={false}.

Examples

Alert dialog

role="alertdialog" is for confirmations that need an explicit decision. It can't be dismissed by clicking outside and hides the close icon. DialogIcon adds a tinted status icon.

Sizes

sm (384px), md (512px, default), lg (672px), xl (896px) set the maximum width. The dialog shrinks to fit smaller viewports.

Full screen

size="full" fills the viewport with a 1rem margin — useful for editors, previews and complex forms on small screens. Give the main element flex-1 to fill the space between header and footer.

Scrollable content

Dialogs never grow taller than the viewport. Wrap long content in DialogBody and it scrolls while the header and footer stay put. Pass a max-h-* class to DialogContent for a shorter cap.

Forms

Wrap the content in a React Aria Form and use the render-function child, ({ close }) => …, to close the dialog after a successful submit.

Validation and async submit

Field validation runs before onSubmit. Show isPending on the submit button while the request runs, then close and confirm with a toast.

Controlled

Render DialogContent without a trigger and control it with isOpen and onOpenChange. Use this to open a dialog from a menu item, a keyboard shortcut or after an async event. Focus returns to the element that was focused before opening.

Non-dismissable

isDismissable={false} ignores outside clicks, isKeyboardDismissDisabled ignores Esc, and showCloseButton={false} hides the ✕. Use sparingly — users should always have a visible way out.

Recipes

Type to confirm

For irreversible, high-impact actions, require the user to type the resource name before the destructive button is enabled.

Invite members

A small form that builds a list, with per-row role Selects inside the dialog.

Multi-step wizard

Keep one dialog open and swap its body. A ProgressBar and "Step x of y" give orientation; Back and Continue replace a stack of nested dialogs.

Accessibility

  • Focus moves into the dialog on open (to the first focusable element, or one with autoFocus), stays trapped inside, and returns to the trigger on close.
  • Page scroll is locked while open, and content outside the dialog is hidden from screen readers with aria-hidden.
  • DialogTitle provides the accessible name. Without a visible title, pass aria-label to DialogContent.
  • role="alertdialog" tells screen readers the dialog needs an immediate response.
  • Put autoFocus on the least destructive action in alert dialogs, or on the first field in forms.
  • Nested overlays (selects, menus, popovers) inside a dialog work as expected; Esc closes the innermost one first.

Keyboard

KeyAction
Tab / Shift+TabCycles focus within the dialog
EscCloses the dialog (unless isKeyboardDismissDisabled or role="alertdialog")

Styling

Data attributes

AttributeOnPresent when
data-enteringoverlay, contentOpening animation is running
data-exitingoverlay, contentClosing animation is running
data-slot="dialog-overlay"backdropAlways
data-slot="dialog-content"panelAlways
data-slot="dialog-body"bodyWhen DialogBody is used

Enter and exit animations use tw-animate-css classes keyed off data-entering and data-exiting, so React Aria waits for them to finish before unmounting.

Customizing

  • className on DialogContent styles the panel (width, height, padding, radius).
  • The backdrop uses the exported overlayStyles string; reuse it for your own modal surfaces.
  • The panel is a flex column capped at the viewport height; the inner Dialog is flex flex-col gap-4 p-6 and scrolls as a fallback if you don't use DialogBody.
  • The inner Dialog padding is p-6. Override with [&>[role=dialog]]:p-0 on DialogContent for edge-to-edge content like images.

API Reference

DialogTrigger

Prop

Type

DialogContent

Prop

Type

DialogIcon

Prop

Type

DialogClose

A Button with slot="close". Accepts every button prop.

Prop

Type

DialogTitle

Accepts every prop of React Aria's Heading, including level to change the heading element.

DialogHeader / DialogBody / DialogFooter / DialogDescription

Accept every prop of <div> / <div> / <div> / <p>.

  • Sheet — a dialog that slides in from the edge.
  • Popover — non-modal content anchored to a trigger.
  • Command Palette — a searchable dialog of actions.
  • Toast — non-blocking feedback after the dialog closes.