Dialog
A window that opens over the page and waits for an answer. It takes your keyboard and attention until you deal with it, and gives them back where it found them.
Preview
dialog · base base · style base-lyraOpen under the product themeInstallation
pnpm dlx shadcn@latest add @opsinjs/dialog
The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.
Usage
import { Dialog } from "@/components/ui/dialog"<Dialog
open={open}
onOpenChange={setOpen}
title="Delete this reading?"
description="It will be removed from your history and from any trends it appears in."
actions={
<>
<Button variant="destructive" onClick={remove}>Delete reading</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Keep it</Button>
</>
}
/>When to use it
Use it when
- A decision the reader must make before anything else can proceed.
- Confirming an action that cannot be undone or that the reader may not have anticipated.
- A short blocking task, such as re-authenticating before revealing sensitive values.
Do not use it when
- The reader could carry on without answering, or you are announcing, not asking.Use
alert-bannerinstead. - A task the reader chose to start and can abandon.Use
sheetinstead. - Asking permission to collect or share health data.Use
consent-sheetinstead. - Confirming something that has already happened.Use
toastinstead. - Supporting detail attached to a control.Use
popoverinstead.
Anatomy
DialogThe portal root, holding the scrim and the panel.Controlled byopenDialog.ScrimThe dimming layer, a Surface at the scrim rung.Dialog.ViewportFixed box; floats low on a phone, centred above sm.Dialog.ContainerThe popup. Carries the role, the name and the description.Controlled byclassNameDialog.HeaderPinned. Holds the title and close control.Dialog.TitleAn h2, always visible, and the accessible name.Controlled bytitleDialog.CloseGlyph and visible word from closeLabel. Absent when severity is alert.Controlled byseverityDialog.BodyThe scroll region, so the actions stay reachable at 200% text.Dialog.DescriptionWhat each answer does, and on an alert dialog why one is needed.Controlled bydescriptionDialog.ContentOptional. Nothing translucent goes in here.Controlled bychildrenDialog.ActionsPinned, least destructive last, at most two. Stacked below sm.Controlled byactions
Examples
Confirming something that cannot be undone
Destructive first, safest last, where initialFocus="safest" lands, so a stray
Return keeps the reading.
dialog-confirming-a-deletion · base base · style base-lyraOpen under the product themeAn answer is needed
severity="alert" sets role="alertdialog", removes the close control, and
refuses both the scrim and Escape.
dialog-an-answer-is-needed · base base · style base-lyraOpen under the product themeContent guidelines
The title asks the question. The description says what each answer does, and on an alert dialog why one is required. Labels are verbs naming the outcome: never OK and Cancel, never a judgement about the reader.
Do
"Delete this reading? It will be removed from your history. [Delete reading] [Keep it]" Question, consequence, two verbs.
Don’t
"Are you sure? [OK] [Cancel]" Sure about what, and which button does which?
Accessibility
Audited against WCAG 2.2 AA: a source pass and a rendered pass. The rendered pass found no axe violations, no undersized target, no focus-visible miss, and held at 320px. The source pass found no critical, serious or moderate defect. The audit is author-run, not independent, and clinical review is pending.
Confirmed:
severity="alert"swaps in Base UI's alert-dialog root, tying the role to the refusal to dismiss.- Named by its title, described by its description; a blank description renders nothing.
- Focus moves in on open, is trapped, and returns on close to whatever had it.
- The page behind is
aria-hidden, notinert, so focus from behind can still reach it.
One residual: Dialog.Close uses a raw focus-visible utility, not the
border-focus token, but renders a valid 2px offset ring, so 2.4.7 and 1.4.11 pass;
it matches the reviewed sheet close control by design.
| Key | Action | Notes |
|---|---|---|
| Tab | Moves focus to the next control inside the dialog | Trapped; initialFocus="safest" wraps the first Tab to the top. |
| Shift+Tab | Moves focus back to the previous control | Reverse order matches visual order. |
| Enter | Activates the focused action | With initialFocus="safest", the answer that changes nothing. |
| Space | Activates the focused action | Consumed by the button. The page behind is scroll-locked. |
| Escape | Closes an ordinary dialog and returns focus to whatever opened it | The scrim dismisses the same way; both refuse when severity is alert. |
| Escape | Does not close an alert dialog; moves focus to the dialog itself | Re-reads the named, described container. The reason is not spoken. |
| Pair | Theme | APCA Lc | WCAG 2.2 | Floor |
|---|---|---|---|---|
| body text on the page | light | 101.6 | 17.18:1 | Pass |
| body text on the page | dark | -100.5 | 18.00:1 | Pass |
| body text on a card | light | 104.7 | 17.96:1 | Pass |
| body text on a card | dark | -99.6 | 16.32:1 | Pass |
| body text on the muted ground | light | 98.1 | 16.32:1 | Pass |
| body text on the muted ground | dark | -97.5 | 13.77:1 | Pass |
| secondary text on the page | light | 83.9 | 7.42:1 | Pass |
| secondary text on the page | dark | -80.7 | 13.44:1 | Pass |
| secondary text on a card | light | 87.0 | 7.76:1 | Pass |
| secondary text on a card | dark | -79.8 | 12.19:1 | Pass |
| secondary text on the muted ground | light | 80.4 | 7.05:1 | Pass |
| secondary text on the muted ground | dark | -77.8 | 10.28:1 | Pass |
| a hairline boundary on the page | light | 19.3 | 1.41:1 | Below floor |
| a hairline boundary on the page | dark | -8.2 | 1.90:1 | Below floor |
| a hairline boundary on a card | light | 22.4 | 1.47:1 | Below floor |
| a hairline boundary on a card | dark | -7.3 | 1.72:1 | Below floor |
| a hairline boundary on the muted ground | light | 15.8 | 1.34:1 | Below floor |
| a hairline boundary on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on the page | light | 69.8 | 4.52:1 | Pass |
| a placeholder boundary on the page | dark | -51.0 | 7.62:1 | Pass |
| a placeholder boundary on a card | light | 72.8 | 4.72:1 | Pass |
| a placeholder boundary on a card | dark | -50.1 | 6.91:1 | Pass |
| a placeholder boundary on the muted ground | light | 66.2 | 4.29:1 | Pass |
| a placeholder boundary on the muted ground | dark | -48.1 | 5.83:1 | Pass |
| a placeholder fill on the page | light | 8.3 | 1.18:1 | Below floor |
| a placeholder fill on the page | dark | -8.2 | 1.90:1 | Below floor |
| a placeholder fill on a card | light | 11.4 | 1.23:1 | Below floor |
| a placeholder fill on a card | dark | -7.3 | 1.72:1 | Below floor |
| a placeholder fill on the muted ground | light | 0.0 | 1.12:1 | Below floor |
| a placeholder fill on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on its own fill | light | 58.9 | 3.84:1 | Pass |
| a placeholder boundary on its own fill | dark | -41.5 | 4.01:1 | Below floor |
| the card hairline on the page | light | 69.8 | 4.52:1 | Pass |
| the card hairline on the page | dark | -28.7 | 4.19:1 | Below floor |
| the card hairline on a card | light | 72.8 | 4.72:1 | Pass |
| the card hairline on a card | dark | -27.9 | 3.80:1 | Below floor |
| body text on the canvas material | light | 104.7 | 17.96:1 | Pass |
| body text on the canvas material | dark | -100.5 | 18.00:1 | Pass |
| body text on the inset material | light | 98.1 | 16.32:1 | Pass |
| body text on the inset material | dark | -100.1 | 17.18:1 | Pass |
| body text on the card material | light | 104.7 | 17.96:1 | Pass |
| body text on the card material | dark | -99.6 | 16.32:1 | Pass |
| body text on the raised material | light | 104.7 | 17.96:1 | Pass |
| body text on the raised material | dark | -97.5 | 13.77:1 | Pass |
| body text on the sheet material over the darkest backdrop | light | 99.9 | 16.76:1 | Pass |
| body text on the sheet material over the darkest backdrop | dark | -99.7 | 16.47:1 | Pass |
| body text on the sheet material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the sheet material over the lightest backdrop | dark | -80.8 | 6.07:1 | Pass |
| body text on the overlay material over the darkest backdrop | light | 85.9 | 13.48:1 | Pass |
| body text on the overlay material over the darkest backdrop | dark | -99.9 | 16.77:1 | Pass |
| body text on the overlay material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the overlay material over the lightest backdrop | dark | -54.2 | 2.69:1 | Below floor |
These are the measured token pairs this component draws colour from, not a measurement of the component itself.
API reference
Prop
Type
Generated from DialogProps in registry/bases/base/dialog.tsx.
actions and closeLabel are not in the specification; closeLabel defaults to
Close, ignored on an alert dialog. onOpenChange does not fire when Escape is
refused. sheetBelow is not a prop: the dialog never becomes a Sheet.
Related
- Sheet: a place the reader chose to go and may leave; Dialog never becomes one.
- AlertBanner: announces without blocking a reader who can carry on.
- ConsentSheet: a request with a consent contract and a recorded decision.
- Popover: anchored and non-modal, dismissed by leaving rather than answering.
Sheet
A panel that slides up from the bottom, stops at sensible heights, and can be dragged or dismissed. The one-handed way to act without leaving the page.
Callout
A short piece of set-apart information that helps you understand what you are reading without claiming anything about your health. A note, a tip or a caveat.