Dialog

Holds the page for a short task or a choice that must finish first. A grey shell holding one white card (the title, the X and the content), with the actions on the grey.

Examples

A short form

A yes or no question

Type the name to delete

Sizes

  • sm

  • defaultdefault

  • lg

States

  • rest

  • hover

  • focus

  • active

  • disabled

Usage

When to use it
Use
The person must finish or abandon a short task before the page makes sense again: rename, create a key, edit a secret.
Use
An action needs one yes or no and the page should not change until it gets one: stop a run, revert a prompt release.
Use
The action cannot be undone and deletes something with a name: an agent, a member, a key.
Not for
The task takes more than one screen of fields, or the page must stay beside it. Use Sheet.
Not for
A few lines next to a control and the page stays usable. Use Popover.
Not for
The message reports a result and needs no answer, or the action is undoable (toast with Undo). Use Toast.

Rules

12 rules
Must
Pass open and onOpenChange; never render the dialog conditionally. Because defaultOpen is read once, so after Escape the parent still holds editing and the dialog will not open again.
Must
Return the server promise from onAction; never close in onClick. Because closing in onClick drops a failed save 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 Delete.
Must
Use role="alertdialog" with confirmText and a DialogConfirmField to delete for good a thing that holds others, such as an agent, a workspace or a data source; one record, such as a prompt release or a run, takes a plain alertdialog. Because a stray click or Enter deletes it for good, and typing the name makes the person read which one, while typing for every record trains them to type through the real one.
Never
Confirm an undoable action; do it and show a toast with Undo. Because a confirm on every undoable action trains people to click through, so they click through the real one too.
Must
Title a dialog in a few words; put the explanation in DialogDescription. With one field, the title names it: give the field an aria-label, not a second visible title. Because the title is a 16px heading on the X's line, so a sentence wraps beside the X and pushes the body down.
Must
Title an alert as a question ("Stop run 4812?") and work as a verb phrase ("Rename agent"); never "Are you sure?". Because "Are you sure?" names neither the action nor the object, so a screen reader opening it hears no context.
Should
Repeat the title verb on the primary ("Stop run") and label the dismiss with what it keeps or "Cancel".
Should
State the consequence with numbers: what is lost, what keeps working, for how long.
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".
Should
Let the footer decide focus on open, or mark one element data-autofocus; never focus the panel yourself.
Never
Restyle the backdrop or shadow; pass only width or layout classes. Because the 24% backdrop and the frame line in shadow-overlay are shared by every overlay, so a custom one stands apart from Sheet and Popover.

Accessibility

6 checks
The panel is role="dialog" or "alertdialog" named by DialogTitle, described by DialogDescription, with aria-modal.
Focus lands from the footer default or data-autofocus; Tab cycles inside; the page behind is inert.
Escape closes this dialog only and does nothing while pending.
Enter presses the footer default; in a typed-confirm field it does nothing.
A failed action shows a role="alert" message above the footer.
The corner X is a button named "Close", disabled while pending.

Edge cases

12 edge cases
112-character title and 300-character description
Wrap inside 400px; the X keeps its corner.
A long ID to name
Title it by the readable name ("Delete refund-prompt v14?") and put the ID in DialogDescription in code; an ID in the title breaks mid-word.
The deleted row is gone
Pass DialogContent finalFocus the next row's actions button, else the previous row's, else the table, since the trigger left with the row.
40 rows at 390x640
The panel stops at 100dvh minus 64px and scrolls inside; past 3 screens use a Sheet.
Right to left
The X moves to the top left and the footer reverses.
10-second action
The button spins; Cancel, the X, Escape and the backdrop do nothing.
Action rejects
The dialog stays open with the typed name and an alert; a retry clears it first.
Popover, menu and child dialog inside
Escape closes the innermost only; the parent dims under its own overlay.
Zoom 200% or 320px
The title wraps and the panel scrolls; the footer buttons wrap and stay at the end.
Touch
Buttons stay at the end at their own width and grow to 44px; focus starts on the panel so the keyboard stays down.
Forced colors
The panel gets a 1px CanvasText outline.
Reduced motion
Open and close cut; a running action's arc stops turning but stays drawn.

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
DialogContent initialFocus, finalFocus
RefObject<HTMLElement | null> | ((type) => HTMLElement | boolean | null) | boolean default first control, the trigger
DialogFooter defaultAction
'primary' | 'dismiss' | 'none' default auto
DialogAction onAction
() => void | Promise
DialogAction close
boolean default true with onAction
DialogAction
ButtonProps
DialogConfirmField label
(name) => ReactNode default "Type <name> to confirm"

Tokens

25 tokens
--surface-overlay
oklch(var(--ink-c) / 0.24)
--surface-page
var(--paper)
--elev-overlay
0 0 0 1px var(--line-frame), 0 12px 24px -12px oklch(var(--text-on-field-c) / var(--shadow-alpha))
--radius-frame
10px
--overlay-sm
400px
--overlay
480px
--overlay-lg
640px
--gutter-sm
16px
--space-8
32px
--dur-slow
180ms
--dur-base
160ms
--ease-step
cubic-bezier(0.2, 0, 0, 1)
--type-label
400 14px/16px var(--font-sans)
--type-body-sm
380 13px/18px var(--font-sans)
--type-tag
var(--type-code-sm)
--text-1
var(--ink)
--text-2
oklch(var(--ink-c) / 0.80)
--text-3
oklch(var(--ink-c) / 0.66)
--line-row
oklch(var(--ink-c) / 0.08)
--line-control
oklch(var(--ink-c) / 0.16)
--accent-mark
var(--ink)
--accent-wash
var(--surface-selected)
--accent-line
var(--ink)
--dur-spin
800ms
--ease-spin
linear

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.