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 ·
Installation
bunx --bun shadcn@latest add @dga/dialogUsage
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.
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.
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.
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.
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.
Accessibility
| Key | Action |
|---|---|
| Tab | Moves focus to the next control inside the dialog. At the last control it goes back to the first: focus never leaves the dialog. |
| Shift + Tab | Moves focus back. |
| Escape | Closes the dialog and returns focus to the control that opened it. |
| Enter | Presses the focused button. |
- Built on Base UI Dialog:
role="dialog",aria-modal,aria-labelledby(DialogTitle) andaria-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
disablePointerDismissalon 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.
| Prop | Type | Default |
|---|---|---|
booleanWhether the dialog is initially open. To render a controlled dialog, use the `open` prop instead. | No default | |
| ||
booleanWhether 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) => voidEvent handler called when the dialog is opened or closed. | No default | |
| ||
booleanWhether 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.
| Prop | Type | Default |
|---|---|---|
"sm" | "md" | "lg"Width: `sm` 400px, `md` 600px (DGA), `lg` 800px. All shrink to fit a phone. | "md" | |
| ||
booleanShows the close button in the corner. Turn it off when the footer has its own. | true | |
| ||
stringAccessible 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
smandlgwidths are added. - The backdrop is
alpha-black-50. On phones the card is 100% wide minus 16px of page padding.
| Change | Reason |
|---|---|
Dialog: name, close and focus | A 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. |
Contrast
14 pairs checked · all pass WCAG 2.1 AAShow 7 statesHide
| Variant / state | Light | Dark |
|---|---|---|
| title | 14.53:1 AAA | 15.87:1 AAA |
| description | 10.17:1 AAA | 12.04:1 AAA |
| body text | 18.09:1 AAA | 15.87:1 AAA |
| close icon | 18.09:1 3:1 | 15.87:1 3:1 |
| close icon, hovered | 16.44:1 3:1 | 9.59:1 3:1 |
| close icon, pressed | 14.61:1 3:1 | 6.95:1 3:1 |
| focus ring | 18.09:1 3:1 | 17.49:1 3:1 |
Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report