# Dialog

A dialog asks for a decision or reports a result on top of the page. It's called Modal in Figma.

## When to use

- To confirm an action before it runs, like a withdrawal, a bid or removing a team member.
- To collect a few inputs without leaving the page, like editing a profile.
- To report the result of something the person just did, like a completed swap.

## When not to use

- For a note that needs no answer, use a toast.
- For a warning that belongs to part of the page, use an alert there.
- For something people do often, keep it on the page so it doesn't interrupt them.
- For long tasks, big tables or anything people should link to, use a page. If the content doesn't fit, try a bigger `size` first, then a page.
- Never open a dialog from another dialog. Use steps with a back button instead.

## Anatomy

## Types

### Decision

Use `decision`, the default, when the dialog asks for a choice or some input: confirming, filling a form, picking an option. The header and body sit on a white panel, and the actions sit on the grey tray under it, right-aligned. The title is heading/sm and the description one line of body/sm. A confirm puts its explanation in the body as `DialogDescription` (body/md).

### Standalone

Use `standalone` to report a result: success, an error, a finished transaction or an announcement. It's one white surface with a 96px illustration, a centred title (heading/md) and description (body/md), and buttons of equal width.

A flow that ends in a result can switch `type` on its last step.

### Alert

Pass `alert` for a confirm that can't be undone, like removing a team member. It's a decision dialog with a Destructive main action and no close button. Screen readers announce it as an alert (`role="alertdialog"`), and a click outside doesn't close it, so it can't be dismissed by accident. Put *Cancel* first so it gets focus when the dialog opens.

### Without a footer

A picker, where choosing an option is the action, has no footer. Keep the close button so there is a way out.

## Actions

- Put one main action on the right, as a Primary button. Use Destructive instead when it can't be undone.
- Put the dismiss action, like *Cancel* or *Not now*, to its left as a Secondary button.
- Use two buttons at most. If you need a third, the dialog is probably doing too much.
- On a phone the buttons stack at full width, with the main action on top.
- Show the close button (X) only when no footer button closes the dialog. Turn it off with `showClose={false}`.
- In a flow with steps, go back with the header's back button (`onBack`), never with a *Back* button in the footer.

## Content

- Name the action or the result in the title as a verb and a noun, in sentence case, like *Confirm withdrawal*, *Remove collaborator?* or *Swap complete*.
- Keep the header description to one line on what happens. It truncates past one line, so anything longer, like what can't be undone, goes at the top of the body as `DialogDescription`.
- Button labels repeat the title's verb, like *Withdraw* for *Confirm withdrawal*. Avoid *OK*, *Yes* and *Submit*.

## Behaviour

| Key | Action |
| --- | --- |
| <kbd>Tab</kbd> | Moves focus through the dialog. Focus stays inside it. |
| <kbd>Esc</kbd> | Closes the dialog. |

- It opens in 250ms, fading in and scaling up from 96%, and closes in 180ms on a gentler ease-out back to 96%, with the scrim moving in step. With reduced motion it only fades.
- Keyboard focus shows a 2px ring with a 2px gap on every button. The header sits above the body, so the ring is never cut off.
- Opening moves focus to the first thing you can use: the back or close button, the first field, or *Cancel* in an alert. Closing returns focus to the button that opened the dialog.
- To start focus somewhere else, handle `onOpenAutoFocus`: call `preventDefault()` and focus a control inside the dialog yourself. Cancelling it alone leaves focus behind the scrim.
- Clicking the scrim closes the dialog, except an alert.
- When the content is taller than the screen, the body scrolls and the header and footer stay put.
- While a transaction is running, pass `dismissible={false}`. It turns off <kbd>Esc</kbd>, the scrim click and the close button together.

## Accessibility

- Every dialog needs a title, which names it for screen readers. If the design hides it, keep it with `srOnlyTitle`.
- Use `alert` for destructive confirms, so screen readers announce them as alerts.
- The back and close buttons have the labels *Back* and *Close*.
- The dialog traps focus and hides the page from assistive tech while it's open.

## Props

### DialogContent

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `type` | `"decision"` \| `"standalone"` | `"decision"` | Decision asks, standalone reports a result. |
| `size` | `"sm"` \| `"md"` \| `"lg"` \| `"xl"` \| `"2xl"` | `"md"` | Max widths 400, 440, 540, 640 and 860px. |
| `dismissible` | `boolean` | `true` | Off while a transaction runs. |
| `alert` | `boolean` | `false` | A destructive confirm: `role="alertdialog"`, no closing from outside. |

### DialogHeader

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `title` | `ReactNode` | | Required. |
| `description` | `ReactNode` | | One or two lines under the title. |
| `illustration` | `ReactNode` | | The 96px illustration in a standalone dialog. |
| `onBack` | `() => void` | | Shows the back button. |
| `showClose` | `boolean` | `true` | Turn off when a footer button closes the dialog. |
| `srOnlyTitle` | `boolean` | `false` | Hides the header but keeps the title for screen readers. |
| `media`, `badge` | `ReactNode` | | Code only: an avatar before the title, a badge before the close button. |

`DialogBody` and `DialogFooter` take `children` and `className`. `Dialog`, `DialogTrigger` and `DialogClose` are Radix Dialog's.

## Related

- A [Button](/components/button/) runs the dialog's actions.
