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 confirmText
string
Dialog open, defaultOpen, onOpenChange
Base UI default false
DialogContent size
'sm' | 'default' | 'lg' default 'default'
DialogContent showCloseButton
boolean default role === dialog
DialogHeader meta
ReactNode
DialogFooter defaultAction
'primary' | 'dismiss' | 'none' default auto
DialogFooter meta
ReactNode
DialogAction onAction
() => void | Promise
DialogAction close
boolean default true with onAction
DialogConfirmField label
(name) => ReactNode default "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)

Install

For your coding agent

GitHub

Please star me pookie

198

A new design system every week

Leave your email before you copy the prompt.