Dialog
A modal panel over a dimmed scrim. It traps focus, closes on Esc or a click outside, and hands focus back to the trigger. Built on Radix Dialog. Source: src/components/ui/dialog.tsx.
Default
A short form in a dialog. Submit closes it and the value updates behind it; Cancel, Esc and the X all close without saving.
Confirm a destructive action
There is no AlertDialog primitive yet, so a confirm is composed from Dialog. Drop the X, put the safe choice first and give it focus, and name the action on the destructive button.
Footer close
For a read-only notice, swap the corner X for the footer's built-in Close pill: showCloseButton={false} on the content, showCloseButton on the footer.
Long content
Known gap: DialogContent caps its height at --dialog-safe-max-h but has no overflow of its own, so long content spills past the rounded panel. Until it does, give the middle row min-h-0 and overflow-y-auto; header and footer stay pinned and the body scrolls between hairlines.
Motion and layering
Enter and exit are CSS keyframes (tw-animate), so a dialog closed mid-entrance finishes its exit from wherever it is rather than reversing smoothly. Under reduced motion the global rule cuts both to near-zero.
| Value | Token | Usage |
|---|---|---|
| 50 | Overlay and panel. The brand ladder (--z-modal 200) is defined but not wired in yet. | |
| scrim | The scrim: a dim wash with a light blur behind the panel. | |
| 200ms | 200ms enter: scrim fades in, panel fades and scales from 95%. | |
| 90ms | 90ms exit: both leave faster than they came. | |
| expo-out | The panel's curve: quick start, long settle. | |
| 0.95 | Scale from --scale-enter (0.95), never from 0. | |
| max-h | Height cap so the panel clears the viewport edges and the keyboard on a phone. |
Props
| Part / prop | Type | Default | Description |
|---|---|---|---|
| Dialog | Radix Dialog.Root | — | open, defaultOpen, onOpenChange, modal. State lives here. |
| DialogTrigger | Radix Dialog.Trigger | — | Use asChild around a Button. Gets aria-expanded and focus back on close. |
| DialogContent | Radix Dialog.Content | — | Portals itself and renders its own overlay. Takes onEscapeKeyDown, onPointerDownOutside, onOpenAutoFocus. Width via className (sm:max-w-md). |
| showCloseButton | boolean | true | The 32px X in the top-right corner, with a sr-only “Close” label. |
| DialogHeader | ComponentProps<"div"> | — | Title and description stack. Centred on a phone, left from sm. |
| DialogTitle | Radix Dialog.Title | — | Required for the accessible name. 24px heading face. |
| DialogDescription | Radix Dialog.Description | — | Wired to aria-describedby. Omit it and pass aria-describedby={undefined} on the content. |
| DialogFooter | ComponentProps<"div"> | — | Actions, right-aligned from sm; stacked with the primary on top on a phone. |
| showCloseButton | boolean | false | Appends a secondary “Close” pill after the children. |
| DialogClose | Radix Dialog.Close | — | Any element that closes the dialog. Use asChild around a Button. |
| DialogPortal, DialogOverlay | Radix | — | Exported for custom shells. DialogContent already includes both. |
Best practices
When to use
- A dialog interrupts. Use it for a decision or a short form that must finish before the page continues.
- Long or browsable content belongs in a Sheet or its own page, not a tall dialog.
- Destructive actions confirm here, with the safe choice focused first. There is no AlertDialog yet.
Behavior
- Esc, a scrim click and the X all close. Use onPointerDownOutside to block scrim dismissal only when input would be lost.
- Focus moves into the panel on open and back to the trigger on close; don't fight either.
- Wrap fields in a form so Enter submits; close in the submit handler once the save succeeds.
Accessibility
- Every dialog needs a DialogTitle; hide it with sr-only if the design has no visible heading.
- DialogDescription becomes aria-describedby. Keep it to one or two sentences.
- The corner X has a sr-only “Close” label; a custom close button needs one too.
Content
- Title as a question for a confirm (“Delete this thread?”), as a verb phrase for a task (“Rename agent”).
- Button labels say what happens: Delete, Save, Keep thread. Not OK and Cancel.