Skip to content

Dialog

A window over the page for a decision or a short task. Three widths, a close button, and focus trapped inside and returned to the trigger.

Version 0.4.1 ·

Dialog

Installation

bunx --bun shadcn@latest add @dga/dialog

Usage

page.tsx
tsx
import { Button } from "@/components/ui/button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";

export function CancelRequest() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>Cancel request</DialogTrigger>
      <DialogContent closeLabel="Close">
        <DialogHeader>
          <DialogTitle>Cancel this request?</DialogTitle>
          <DialogDescription>Request 24810 will be withdrawn. You can submit it again later.</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose render={<Button variant="outline" />}>Keep request</DialogClose>
          <DialogClose render={<Button destructive />}>Cancel request</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Use a Dialog when the person must answer or finish something before going on: confirm a destructive action, edit a few fields, read terms. Do not use it for a message that needs no answer (use Toast or Inline Alert) or for a long page of content. Always give it a DialogTitle: it names the dialog for screen readers. Put the main action last, and name actions by what they do ("Cancel request"), not "Yes" and "No". closeLabel is the accessible name of the close button; translate it on Arabic pages.

Anatomy

  • Backdrop: dims the page and makes it inert.
  • Content (role="dialog"): the card. 600px wide by default, at most the height of the screen, scrolling inside.
  • Header: DialogTitle and DialogDescription.
  • Body (optional): DialogBody, for fields or text.
  • Footer: the actions, aligned to the end. DialogClose closes the dialog.
  • Close button: in the corner, on the end side.

Examples

Sizes

size is sm (400px), md (600px, the DGA width) or lg (800px). All of them shrink to fit a phone.

Sizes

With a form

Put fields in DialogBody. The dialog scrolls when it is taller than the screen. A Field inside is labelled and wired as usual.

With a form

Must be answered

Set disablePointerDismissal on Dialog for a decision that cannot be skipped: a click outside the window no longer closes it, and Escape still does. Pair it with showCloseButton={false} so the only ways out are the actions in the footer.

Must be answered

Controlled

Pass open and onOpenChange to own the state: open the dialog from your own code, and close it when a request finishes. The trigger is optional.

Controlled

RTL

The close button moves to the left, the footer actions are aligned to the left, and the window fades and scales the same way. Nothing is mirrored by hand: the layout uses start and end.

LTR (English)
RTL (Arabic)

Accessibility

KeyAction
TabMoves focus to the next control inside the dialog. At the last control it goes back to the first: focus never leaves the dialog.
Shift + TabMoves focus back.
EscapeCloses the dialog and returns focus to the control that opened it.
EnterPresses the focused button.
  • Built on Base UI Dialog: role="dialog", aria-modal, aria-labelledby (DialogTitle) and aria-describedby (DialogDescription).
  • Focus moves into the dialog when it opens, is trapped there, and returns to the trigger when it closes. The page behind is inert and its scroll is locked.
  • Set disablePointerDismissal on Dialog for a decision that must be answered, so a click outside does not close it. Escape still closes it.
  • The close button has an accessible name (closeLabel). Titles and text are 4.5:1 and icons 3:1 or better, in light and dark.
  • The window fades and scales in; with reduced motion it appears without animation.

API Reference

Dialog

A window over the page for a decision or a short task (DGA: Modal). Compose it from DialogTrigger and DialogContent. Focus moves into the window and stays there, the page behind it is inert, Escape closes it, and focus returns to the trigger.

Dialog props
PropTypeDefault
boolean

Whether the dialog is initially open. To render a controlled dialog, use the `open` prop instead.

No default
boolean

Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it.

No default
boolean | "trap-focus"

Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. When `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so touch screen readers can escape the popup.

No default
(open: boolean) => void

Event handler called when the dialog is opened or closed.

No default
boolean

Whether the dialog is currently open.

No default

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogBody

The scrollable middle of a tall dialog.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogClose

Closes the dialog. Render a Button with it: `<DialogClose render={<Button />}>`.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogContent

The window: a dimmed backdrop and the card. It is centred and scrolls when it is too tall.

DialogContent props
PropTypeDefault
"sm" | "md" | "lg"

Width: `sm` 400px, `md` 600px (DGA), `lg` 800px. All shrink to fit a phone.

"md"
boolean

Shows the close button in the corner. Turn it off when the footer has its own.

true
string

Accessible name of the close button. Translate it on Arabic pages.

"Close"

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogDescription

Explains the dialog. Linked to the dialog with `aria-describedby`.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogFooter

The actions, aligned to the end. Put the main action last.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogHeader

Groups the title and description at the top of the window.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogTitle

The name of the dialog. Screen readers announce it when the dialog opens.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

DialogTrigger

The button that opens the dialog.

Also accepts every prop of the Base UI Dialog (opens in a new tab).

Deviations from DGA

  • The Figma file draws only a 600px card. The close button, the focus trap, the Escape key and the sm and lg widths are added.
  • The backdrop is alpha-black-50. On phones the card is 100% wide minus 16px of page padding.
ChangeReason
Dialog: name, close and focusA window that takes over the page must be operable by keyboard and announced (WAI-ARIA dialog pattern, WCAG 2.1.1, 2.4.3). Built on Base UI Dialog.

View all 1 deviations for this component

Contrast

14 pairs checked · all pass WCAG 2.1 AAShow 7 states
Variant / stateLightDark
title14.53:1 AAA15.87:1 AAA
description10.17:1 AAA12.04:1 AAA
body text18.09:1 AAA15.87:1 AAA
close icon18.09:1 3:115.87:1 3:1
close icon, hovered16.44:1 3:19.59:1 3:1
close icon, pressed14.61:1 3:16.95:1 3:1
focus ring18.09:1 3:117.49:1 3:1

Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report