Dialog
Asks for a decision on top of the page.
Role
dialogdefault
alertdialog
alertdialog + confirmText
Sizes
sm
defaultdefault
lg
States
open
pending
failed
typed differs
typed matched
Usage
When to use it
- Use
- The person must finish or abandon a short task before the page makes sense again: rename a pipeline, add a worker.
- Use
- An action needs one yes or no before anything changes: cancel a running job, pause a queue.
- Use
- The action cannot be undone and destroys something with a name: drop a table, delete a pipeline.
- Not for
- The task needs more than a screen of fields, or the list must stay in view beside it. Use Sheet.
- Not for
- A few lines next to a control, and the page stays usable. Use Popover.
- Not for
- The action can be undone; do it and say so. Use Button.
Rules
9 rules
- Must
- End the footer with DialogClose and at most one DialogAction; make it variant="destructive" when the action loses data. Because the footer is one group of actions, and a second slab splits the eye; the slab and the destructive are one family, so both count.
- Must
- Pass open and onOpenChange; never render the dialog conditionally. Because a conditional dialog unmounts before its close fades and Base UI cannot return focus to the trigger.
- Must
- Return the request's promise from onAction; never close the dialog in onClick. Because closing in onClick drops a failed request without a word, where onAction holds the dialog pending and shows the error.
- Never
- Set defaultAction="primary" beside a destructive action; leave it unset or "dismiss". Because Enter outside a field would then press Drop.
- Must
- Use role="alertdialog" with confirmText and a DialogConfirmField to destroy a thing that holds others, such as a table or a pipeline; one row takes a plain alertdialog. Because a stray click or Enter destroys it for good, and typing the name makes the person read which one; typing for every row trains them to type through the real one.
- Must
- Title the dialog name first in the header voice, mono as written: the action and the thing's name ("deploy ingest-events v14", "cancel job_4812?"); never "Are you sure?". Because a screen reader opening the dialog hears the title first, and "Are you sure?" names neither the action nor the object.
- Must
- Keep DialogHeader to the title, meta and X; open DialogBody with DialogDescription; leave the insets to the parts. Because the header is the 38px region header every frame opens with, and header, body and footer share the 12px inset, so a sentence or a padding class in the header breaks the one edge they start on.
- Must
- Give every dialog a DialogTitle, with className="sr-only" when the design shows none. Because Base UI names the dialog from DialogTitle, so without one a screen reader announces only "dialog".
- Never
- Add a shadow, a blur or a rounded corner to DialogContent; pass size or placement classes only. Because a dialog stands out by the scrim behind it and its rule edge, not by a shadow.
State order
4 states, highest first
- 1
- pending
- 2
- failed
- 3
- typed differs
- 4
- open
Accessibility
5 checks
- The popup is role="dialog" or "alertdialog", named by DialogTitle and described by DialogDescription.
- Focus lands from the footer default or a data-autofocus element; Tab cycles inside; the page behind is inert.
- The edge is --line, the rule colour; the scrim sets the dialog apart from the page, and its title names it.
- A failed action shows a role="alert" row above the footer, the failure word on its tint at 5.7:1, with an icon.
- The typed-confirm field is described by its hint and announces only the match, not every keystroke.
Keyboard
3 keys
- Escape
- Closes this dialog only and returns focus to the trigger; nothing while an action runs.
- Enter
- Presses the footer's default action when focus is not on a control; never confirms a typed name.
- Tab
- Moves through the dialog's controls and wraps from the last to the first.
Edge cases
6 edge cases
- The dropped row is gone
- Pass finalFocus the next row's actions button, since the trigger left with the row.
- More rows than the screen holds
- The dialog stops 32px from the top and bottom; the body scrolls under the ruled header.
- A 10-second action
- The action shows the pending mark; Cancel, the X, Escape and the scrim do nothing until it settles.
- Phone, 390 wide
- The dialog leaves 16px of page each side; the footer wraps its buttons before they shrink.
- A menu or a second dialog inside
- Escape closes the innermost; a parent dialog dims under its child.
- Reduced motion
- Open and close cut instead of fading; the pending mark stands still.
API
11 props
Dialog role'dialog' | 'alertdialog'default'dialog'Dialog confirmTextstringDialog open, defaultOpen, onOpenChangeBase UIdefaultfalseDialogContent size'sm' | 'default' | 'lg'default'default'DialogContent showCloseButtonbooleandefaultrole === dialogDialogHeader metaReactNodeDialogFooter defaultAction'primary' | 'dismiss' | 'none'defaultautoDialogFooter metaReactNodeDialogAction onAction() => void | PromiseDialogAction closebooleandefaulttrue with onActionDialogConfirmField label(name) => ReactNodedefault"Type <name> to confirm"
Tokens
29 tokens
--panel- #fafaf7
--line- #e2dfd5
--line-faint- #efede6
--scrim- rgb(22 21 18 / 0.32)
--overlay-sm- 400px
--overlay- 480px
--overlay-lg- 640px
--inset- 12px
--head- 38px
--control-sm- 26px
--space-4- 16px
--space-8- 32px
--type-code- 400 13px/20px var(--font-mono)
--type-code-sm- 400 12px/16px var(--font-mono)
--type-body-sm- 400 14px/20px var(--font-sans)
--type-caption- 400 13px/18px var(--font-sans)
--type-label-sm- 400 12px/16px var(--font-mono)
--text-1- #161512
--text-2- #55524a
--well- #f4f3ee
--fail- #b3261e
--fail-tint- #fdecea
--fail-text- #b3261e
--ok-text- #2c6a3a
--z-overlay- 50
--dur-overlay- 160ms
--dur-state- 120ms
--ease-out- cubic-bezier(0.2, 0.8, 0.2, 1)
--ease-in- cubic-bezier(0.4, 0, 1, 1)