/// Overlay
Dialog
A modal panel over a dimmed backdrop for focused tasks — forms, confirmations, and detail views that demand attention.
import { Dialog } from "@protocore/pds";Basics
Compose the parts: Dialog.Root owns open state, Dialog.Trigger (via asChild) opens it, and Dialog.Content portals the backdrop + centered panel. A Title is required for accessibility; Dialog.Close dismisses.
With a form
Everything inside Dialog.Content is yours to compose — stack Fields and Inputs, then wrap each footer action in Dialog.Close asChild so a click closes the dialog. Focus is trapped until it does.
Controlled
Drive open / onOpenChange yourself to open the dialog from anywhere or to gate closing on an async result. Omit Dialog.Trigger entirely when you open it programmatically.
open = false
When to use it
A Dialog interrupts the flow for a self-contained task the user opts into: an edit form, a multi-step choice, a focused detail view. It dims the page, traps focus, and closes on Esc or backdrop click.
For a destructive or irreversible confirmation, use [AlertDialog](/overlay/alert-dialog) — or its ConfirmDialog convenience — which removes the backdrop-dismiss escape hatch so the choice is deliberate. For a long detail panel or side form that shouldn't cover the whole screen, use a [Sheet](/overlay/sheet). For lightweight, non-modal floating content anchored to a control, use a [Popover](/overlay/popover). Reserve the Dialog for moments that genuinely warrant blocking the rest of the UI.
Compound parts
Dialog is a compound: `Dialog.Root` (state) · `Dialog.Trigger` · `Dialog.Content` (backdrop + panel, renders the close × unless showClose={false}) · `Dialog.Title` (required) · `Dialog.Description` · `Dialog.Footer` (right-aligned action row) · `Dialog.Close`. Compose only the parts you need — but always include a Title, or pass aria-describedby={undefined} when you deliberately omit a Description.
Usage
Do
- Always render a Dialog.Title — Radix warns without one and screen readers need it.
- Wrap footer actions in Dialog.Close so the panel dismisses on click.
- Put the primary action last (rightmost) in the footer, secondary before it.
- Reach for ConfirmDialog when the task is a yes/no destructive confirmation.
Don't
- Nest a Dialog inside another Dialog — redesign the flow instead.
- Use a Dialog for an irreversible action's confirmation — use AlertDialog.
- Cram a whole workflow in; if it scrolls the viewport, use a Sheet or a page.
- Disable Esc / backdrop dismissal on a routine dialog — that's AlertDialog behavior.
Accessibility
| Keys | Action |
|---|---|
| Esc | Closes the dialog and returns focus to the trigger. |
| Tab | Cycles focus forward, trapped within the panel. |
| Shift + Tab | Cycles focus backward, trapped within the panel. |
| Enter / Space | Activates the focused control (e.g. a footer button). |
- Focus is trapped inside the panel while open and restored to the trigger on close.
- Opening the dialog moves focus into the panel; the close × is reachable and labelled.
- The panel is role=dialog with aria-modal; Title labels it and Description describes it.
- Content outside the dialog is inert and hidden from assistive tech while it's open.
Dialog.Content props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | |
className | string | — | |
showClose | boolean | true | Render a top-right 32px ghost × close button. |
style | CSSProperties | — |
Dialog.Footer props
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | |
style | CSSProperties | — |