Dialog
The only component permitted to take the screen away from a clinician. That power is why it is rare, why it is small, and why almost every request for one is better served by something else.
Overview
A dialog interrupts. It steals focus, traps the keyboard, dims the record behind it and refuses to go away until answered. In consumer software that is a mild annoyance. In clinical software it is a deliberate decision to stand between a clinician and a patient's data.
So the bar is high: a dialog is justified only when continuing without a decision would be worse than the interruption. Everything else — status, findings, confirmations, errors — belongs in an Alert Banner, inline, or in a popover.
Rare
Every dialog spends the clinician's attention and the product's credibility. If two appear in one workflow, the workflow is wrong, not under-dialogued.
Self-contained
Everything needed to answer is inside it. A dialog that asks a question whose answer is hidden behind it forces a guess.
Never over an alarm
No dialog may obscure an active Critical or Urgent alarm. The interruption is always subordinate to the patient signal.
When to use
| Use a Dialog | Use something else |
|---|---|
| An irreversible action needs confirmation — discarding a draft report | A reversible action → act immediately, offer undo |
| A decision must be made before the workflow can continue — claiming or discarding another clinician's draft | Information the clinician should see but need not answer → Alert Banner |
| A short, focused input is needed out of context — a reason for emergency access | A form of more than about six fields → its own screen |
| An identity or context boundary is being crossed — switching user | A finding, however serious → Alert Banner. Alarms are not dialogs. |
It is tempting to make a STEMI detection a modal — it feels proportionate to the urgency. It is the wrong call. A modal alarm blocks the very screens the clinician needs to assess it, can only show one alarm at a time, and trains a reflex of dismissing modals fast. Alarms are persistent banners that can be read, ranked and acted on alongside the evidence. See Alert Banner.
Anatomy
| Element | Rule |
|---|---|
| Title | A question or a statement of the decision. Names the object — "Discard this draft report?", never "Are you sure?" |
| Description | What will happen, and to whom. Includes the patient identifier when the action is patient-scoped, because the header may be obscured. |
| Actions | Two, occasionally three. Each label names its outcome. Confirming action last, on the right; cancel first. |
| Scrim | Dims but never hides. The record behind stays legible enough to read an identifier. |
| Close affordance | Present only where dismissal is a safe default. Omitted where a decision is genuinely required. |
Variants
confirm
Two actions, cancel and proceed. The default.
decide
No safe default exists, so there is no cancel and no close affordance — both options are real choices. Used sparingly; the identity-boundary case from Sign in / sign out is the canonical one.
input
A short focused form — at most about six fields. Anything larger is a screen, not a dialog, because a long form inside a modal cannot be cross-referenced against the record behind it.
Note the ordering in that example: access is already granted when the dialog appears. A break-glass prompt that gates entry on typing a justification is a dialog standing between a clinician and a patient in an emergency. Capture the reason; never charge admission for it.
Behaviour
- Focus moves to the dialog on open — to the first interactive element, or the least destructive action. Never to a destructive button.
- Focus is trapped while open and returns to the trigger on close.
- Escape closes
confirmandinput; it does not closedecide, which has no safe default. - Clicking the scrim closes only where Escape does. Never for a destructive confirmation — a stray click is not a decision.
- One at a time. Dialogs never stack. A second request replaces or queues, never layers.
- Alarms outrank dialogs. An arriving Critical alarm renders above the scrim and stays readable. It does not close the dialog, and it does not steal focus — but it must never be hidden by it.
Writing the dialog
- Title names the object. "Discard this draft report?" not "Confirm".
- Description names the consequence and the patient. The header behind is dimmed — identity has to be restated inside.
- Buttons name outcomes, echoing the title. Never "Yes"/"No"/"OK".
- Say what is not affected. "The original ECG and the model finding are unaffected" prevents a clinician hesitating over a safe action.
- No apologies, no exclamation marks. Two to four words per button, 35 % expansion budget for translation.
Object named, patient restated, scope of loss bounded, labels readable out of context.
Sure about what, for whom, losing what? The destructive action is styled as primary and focused by default.
Do's and don'ts
Initial focus on the safe option. Pressing Enter reflexively loses nothing.
Focus on the destructive action, and no patient named. Enter deletes the report.
The alarm renders above the scrim and stays readable. The dialog waits its turn.
A modal over everything. A STEMI has just fired for another patient and nothing on screen says so.
Accessibility
- Radix Dialog primitive, giving
role="dialog",aria-modal, focus trap and restore, and inert background — for free and correctly. aria-labelledbyandaria-describedbywired to the title and description, so the whole question is announced on open.- Initial focus is never destructive. Enter must not delete anything.
- Reachable at 200 % zoom and 320 px: the dialog scrolls internally while the action row stays visible, rather than pushing buttons off-screen.
- Touch targets at
lg— 44 px minimum, with adjacent actions separated by at least 8 px so a mis-tap does not confirm a deletion. - Under
prefers-reduced-motionthe dialog appears without transition, never without the scrim.
Outcomes of use
What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.
- Effectiveness — The decision recorded matches the decision intended. A useful signal is how often a confirmed action is reversed shortly afterwards.
- Efficiency — Every dialog costs an interruption and a context switch. The measure is dialogs encountered per completed workflow, and the target is close to zero.
- Satisfaction — Perceived control. A dialog appearing where a clinician expected none reduces perceived safety even when the question is reasonable.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).
- Alarms render above the scrim and are never obscured. Mitigates: a blocking interaction hiding a patient signal.
- Never used for alarms. Mitigates: single-alarm tunnel vision, and a trained reflex of fast modal dismissal.
- Patient identifier restated inside. Mitigates: confirming a destructive action against the wrong record while the header is dimmed.
- Initial focus never destructive; scrim-click and Escape disabled for destructive confirms. Mitigates: accidental irreversible action.
- Never gates emergency access. Mitigates: authentication or documentation standing between a clinician and a patient.
- Never stacks. Mitigates: a decision made without the context of the one beneath it.
- Labels name outcomes. Mitigates: confirmation blindness.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/dialog.json
import {
Dialog, DialogContent, DialogHeader, DialogTitle,
DialogDescription, DialogFooter,
} from "@/components/ui/dialog"
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent
variant="confirm"
// destructive confirms cannot be dismissed by accident
onEscapeKeyDown={(e) => e.preventDefault()}
onPointerDownOutside={(e) => e.preventDefault()}
>
<DialogHeader>
<DialogTitle>Discard this draft report?</DialogTitle>
<DialogDescription>
Your interpretation for {patient.display} · MRN {patient.mrn} will be
permanently lost. The original ECG and the model finding are unaffected.
</DialogDescription>
</DialogHeader>
<DialogFooter>
// autoFocus on the safe option, never the destructive one
<Button variant="outline" size="lg" autoFocus>Keep editing</Button>
<Button variant="destructive" size="lg" onClick={discard}>
Discard draft report
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | confirm | decide | input |
confirm |
decide removes the close affordance and disables Escape. |
subjectId | string | — | Required for patient-scoped actions; rendered into the description. |
onEscapeKeyDown | (e) => void | closes | Prevent for destructive confirms. |
onPointerDownOutside | (e) => void | closes | Prevent for destructive confirms. |
A dialog rendered with priority or any --alarm-* token in its
subtree fails the build. Alarms are banners; this is enforced rather than documented, for the
same reason the alarm tokens are governed.
Related
- Alert Banner — for anything that is not a required decision.
- Button — action hierarchy and destructive styling.
- Sign in / sign out — the identity-boundary dialog.
- Clinician override — why disagreement gets no confirmation dialog.