Dialog
StableA focused task over a dimmed, softly blurred page. Reach for it when the merchant has to finish or dismiss something before carrying on: a short form, or a question before an action that matters.
Basics
A DialogTrigger opens it and a DialogContent holds the sheet: a DialogHeader with the DialogTitle and DialogDescription, a DialogBody, and a DialogFooter of actions with the primary last. A close button sits in the top right. The sheet rises 10px and settles on the spring; it leaves in half the time.
In both themes
Held open for review, light beside dark. The page behind dims to 34% near-black under a 3px blur (62% in dark), and the sheet has the system's widest corners (20px) on the modal shadow. In dark it lifts to graphite with a faint ring.
Open them for real
Each trigger opens its dialog in the theme of its own side.
Sizes
Three widths. On a screen narrower than the dialog it keeps 16px clear of each edge, and from 480px down its padding tightens to 20px and the footer stacks full width with the primary on top.
| Size | Width | Use it for |
|---|---|---|
sm | 400px | Confirmations, and forms of one or two fields. |
md | 520px | Short forms. The default. |
lg | 720px | Wider content: two columns of fields, a list to review, a small table. |
Long content
The dialog never grows taller than the window less 48px. When its content would, only the DialogBody scrolls: the title and the actions stay in view. The body runs to the sheet's edges, so its scrollbar sits at the edge and focus rings inside it are never clipped.
Without the close button
hideClose drops the top-right close button when the footer already offers a way out, as a Cancel does. Escape still closes it. closeLabel renames the close button for screen readers, for example in another language.
Confirm dialog
ConfirmDialog asks one question before an action that matters. Give it the question as the title, what will happen as the description, and the verb as the confirmLabel. It is an alertdialog: a click outside never closes it.
Tones
tone="critical" is for actions that destroy something or sign out: the confirm pill turns red and first focus goes to Cancel, so Enter cannot destroy anything by accident. Without a tone the confirm pill is primary and takes first focus.
Waiting for the work
Return a promise from onConfirm and the dialog waits for it: the comet stays in orbit round the confirm pill, Cancel is off, and Escape does nothing until the work is done. It closes when the promise resolves and stays open when it rejects, so you can say what went wrong in its children. This refund fails the first time.
Extra content
Anything passed as children sits between the description and the actions: options that go with the action, or an error.
Managing the busy state yourself
Pass loading when the work is tracked somewhere else, such as a mutation. The dialog then shows the busy state while loading is true and leaves closing to you.
Focus and Escape
What happens to focus, the keyboard and the page behind, from opening to closing.
| Dialog | ConfirmDialog | |
|---|---|---|
| Opening | Focus moves to the first thing in the sheet that takes focus. The close button comes last in the page order, so it lands on your content: the email field above. | Focus goes to Cancel when the tone is critical, otherwise to the confirm button. |
| Tab | Moves through the dialog and wraps round. It never reaches the page behind. | The same. |
| Escape | Closes it. | Closes it, except while the work is running. |
| Click on the dimmed page | Closes it. | Does nothing. |
| Closing | Focus goes back to the DialogTrigger. Opened some other way, hand it back yourself with onCloseAutoFocus. | It has no trigger of its own, so hand focus back to the button that opened it (see below). |
| While open | The page behind cannot scroll and is hidden from screen readers. | The same. |
Guidelines
- Title it with the task ("Invite a staff member") or the question ("Delete 3 products?"), never "Confirm" or "Warning".
- Keep the name of the action from the button that opened it to the button that confirms it: Delete 3 products, then Delete products.
- One primary, last in the footer. Use critical only for actions that destroy something or sign out.
- Keep dialogs short. A form that needs a scroll bar or more than a handful of fields wants its own page.
- Never open one dialog on top of another, and never open one on page load.
- After an action that is not reversible, confirm it happened with a quiet message on the page, not another dialog.
Accessibility
- Built on Radix Dialog.
DialogContentis adialognamed by itsDialogTitleand described by itsDialogDescription;ConfirmDialogis analertdialog. - Every dialog needs a
DialogTitle. Give it aDialogDescriptiontoo, or passaria-describedby={undefined}toDialogContent: Radix warns in development when neither is there. Always give aConfirmDialogadescriptionfor the same reason. - Focus is trapped inside while it is open, the page behind cannot scroll, and the rest of the page is hidden from screen readers.
- The close button is named "Close" (
closeLabelchanges it) and comes last in the page order. - While a ConfirmDialog is busy its confirm pill says so (aria-busy) and keeps focus inside the dialog.
- With reduced motion the sheet fades in and out without rising or scaling.
Props
The parts also accept the matching Radix Dialog props.
Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | None | Controlled open state. |
defaultOpen | boolean | false | Starting state when uncontrolled. |
onOpenChange | (open: boolean) => void | None | Called when it opens or closes. |
modal | boolean | true | Trap focus, lock scrolling and hide the page behind while open. |
DialogTrigger, DialogClose
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render your Button as the trigger or the close control. |
DialogContent
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | 400px for confirmations, 520px for short forms, 720px for wider content. |
hideClose | boolean | false | Drop the top-right close button when the footer already offers a way out. |
closeLabel | string | 'Close' | Accessible name of the close button. |
container | HTMLElement | null | document.body | Where the dialog is portalled. |
onOpenAutoFocus | (event: Event) => void | None | Runs as focus moves in. Prevent it and focus something yourself. |
onCloseAutoFocus | (event: Event) => void | None | Runs as focus leaves. Prevent it to send focus somewhere other than the trigger. |
onEscapeKeyDown | (event: KeyboardEvent) => void | None | Prevent it to keep the dialog open on Escape. |
onPointerDownOutside | (event: Event) => void | None | Prevent it to keep the dialog open on a click outside. |
DialogHeader, DialogBody, DialogFooter
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | None | Header: title and description, stacked. Body: scrolls on its own when the dialog meets the window. Footer: actions, right-aligned, primary last. Each accepts every div attribute. |
DialogTitle, DialogDescription
| Prop | Type | Default | Description |
|---|---|---|---|
childrenRequired | ReactNode | None | Title: the task or question, set in Sora. Description: one or two sentences of context. |
asChild | boolean | false | Render your own heading or paragraph element. |
ConfirmDialog
| Prop | Type | Default | Description |
|---|---|---|---|
openRequired | boolean | None | Controlled open state. |
onOpenChangeRequired | (open: boolean) => void | None | Called when it asks to open or close. Ignored while busy. |
titleRequired | ReactNode | None | The question, e.g. "Log out of Orbit?". |
description | ReactNode | None | What happens if they confirm, in a sentence or two. |
confirmLabelRequired | ReactNode | None | The verb that confirms, e.g. "Delete products". Never just "OK". |
cancelLabel | ReactNode | 'Cancel' | The way out. Change it when "Cancel" would be ambiguous, e.g. "Keep order". |
tone | 'critical' | None | Red confirm pill, and first focus on Cancel. |
onConfirmRequired | () => void | Promise<unknown> | None | Runs on confirm. Return a promise to show the busy state: it closes when it resolves and stays open when it rejects. Return nothing and it closes straight away, unless you control loading. |
loading | boolean | None | Controls the busy state yourself. You then close the dialog. |
children | ReactNode | None | Extra content between the description and the actions. |