Design System

Dialogs, modals & drawers

Layered task surfaces for V2: dialogs for focused decisions and forms, drawers for contextual workflows. Both are built on the existing overlay foundation and composed only from existing V2 components.

What is this?

One Dialog foundation, three decision dialogs (confirmation, blocking message, unsaved changes), one form dialog, and a Drawer foundation with detail and filter patterns.

When to use it

  • Dialog — a focused task or decision that must be answered before continuing.
  • Drawer — a secondary workflow or record detail that should keep the page in context.
  • Confirmation and message dialogs — consequential actions and blocking failures.

How to use it

  • Compose header, body and footer; omit what a surface does not need instead of adding empty dividers.
  • Use scroll="region" with DialogBody / DrawerBody so header and actions stay fixed.
  • Guard dismissal on anything holding edits and pair it with UnsavedChangesDialog.

What to avoid

  • A second overlay, backdrop, elevation or focus mechanism.
  • Dialog-specific form controls, buttons or error styles.
  • Dialogs for passive information or plain success feedback.

Dialog

A modal surface for a task that needs the person's full attention. Structure, elevation, backdrop, focus and layering all come from the V2 overlay foundation.

Dialog

Interrupt the page for one focused task or decision, then return the person exactly where they were.

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter } from "@/design-system";

When to use

  • A short task the person must finish or abandon before continuing.
  • A decision that needs acknowledgement — assigning a service, confirming a change.
  • A form of up to roughly a screen of fields.

When not to use

  • Passive information that does not need a response — use a Banner.
  • Confirmation that something succeeded — use a Toast.
  • Contextual detail alongside a list — use a Drawer.

Structure and sizes

Header, optional body and footer. Size follows content complexity — small for a single decision, extra large for dense forms.

Long content — fixed header and footer

Set scroll="region" and wrap the content in DialogBody: only the middle scrolls, so the title stays visible and the actions stay reachable.

Dialog sizes — chosen by content complexity, never by eye.
SizeMax widthUse for
smmax-w-smOne decision or a single field.
md (default)max-w-lgA short form or focused summary.
lgmax-w-2xlA multi-section form.
xlmin(64rem, viewport − 32px)Dense forms, side-by-side fields, embedded tables.

Accessibility

  • Radix renders role="dialog" with aria-modal and wires DialogTitle / DialogDescription as the accessible name and description.
  • Focus moves into the dialog on open, is trapped while it is open, and returns to the trigger on close.
  • The close control is an IconButton with a required label; Escape closes unless dismissal is guarded.
  • Content scrolls inside the dialog while the page behind it stays locked.

Confirmation, alert and unsaved changes

Three decision dialogs built on the alertdialog semantics: confirm a consequential action, acknowledge a blocking message, or guard an exit.

DestructiveConfirmDialog / ConfirmDialog

Ask for confirmation before a consequential or irreversible action, with the verb on the confirm button.

import { ConfirmDialog, DestructiveConfirmDialog } from "@/design-system";

When to use

  • Deleting, archiving or removing a record.
  • An action that is expensive or hard to reverse.

When not to use

  • Routine saves — confirmation friction on every action trains people to ignore it.
  • Reporting a result after the fact — that is a Toast or a MessageDialog.

Delete resident

Destructive tone, destructive confirm button, and a loading state that blocks a duplicate submit.

Accessibility

  • role="alertdialog"; the title and description are announced together on open.
  • Outside click never dismisses; Escape and Cancel both resolve to the safe outcome.
  • The confirm button carries the verb ("Delete resident"), never "OK".

MessageDialog

Block the flow with a message the person must acknowledge, optionally offering a recovery action.

import { MessageDialog } from "@/design-system";

When to use

  • An action failed and the person needs to know why before continuing.
  • A permission or policy refusal that stops the current task.

When not to use

  • Success or progress feedback — use a Toast.
  • Information that stays true for the whole page — use a Banner.

Unable to delete resident

Explain the blocker, then offer the recovery path next to the acknowledgement.

Accessibility

  • role="alertdialog" so assistive technology announces the message immediately.
  • Status is carried by the icon and the copy, not colour alone.
  • The acknowledgement action is always present, even when a retry is offered.

UnsavedChangesDialog

Guard the exit from a surface with pending edits so nothing is lost by accident.

import { UnsavedChangesDialog } from "@/design-system";

When to use

  • Cancel, Escape or outside click on a form dialog or editing drawer that is dirty.
  • Navigating away from an in-progress record edit.

When not to use

  • Read-only surfaces — they should dismiss freely.

Discard changes?

Keep editing, or discard and close.

Accessibility

  • Focus stays inside the guard until the person chooses; "Keep editing" is the safe default.
  • The discard action is labelled with its consequence, never "Yes".

Form dialog

A create or edit task inside a dialog. Every field comes from the existing form layer — the dialog contributes structure, validation placement, submitting behaviour and an exit guard.

FormDialog

Wrap a form in a dialog with a fixed header, a scrolling field region, pinned actions, a form-level error slot and a submit state that preserves what was typed.

import { FormDialog } from "@/design-system";

When to use

  • Creating or editing a record without leaving the current list.
  • A form of up to roughly a screen of fields.

When not to use

  • Long multi-step processes — give those a page.
  • Reference information that sits beside the list — use a Drawer.

Add resident

Field-level validation, a server error, and a submitting state that keeps every value.

Dialog states — the same states as the rest of the V2 feedback system, placed consistently.
StateWhere it appearsBehaviour
Field errorUnder the field, from FormFieldaria-invalid on the control, message wired with aria-describedby.
Form / server errorAlert above the fieldsPersists until the next submit; never replaces the form.
SubmittingSubmit button spinnerCancel, close and dismissal blocked; entered values preserved.
SuccessToast after closeThe dialog closes; a Toast confirms, never a second dialog.

Accessibility

  • The dialog title labels the form; field errors come from FormField and are wired with aria-describedby and aria-invalid.
  • A form-level or server error renders as an Alert above the fields, inside the dialog, before the first control.
  • While submitting, Cancel and dismissal are blocked and the submit button announces its loading state — a second submit is impossible.
  • Guarded dismissal routes Escape and outside click through the unsaved-changes guard instead of discarding silently.

Drawer

A panel that slides in from the edge for a secondary workflow, keeping the page it came from in view. Right is the application convention; bottom is used on mobile-first quick actions.

Drawer

Hold contextual information or a secondary task beside the page, without replacing it.

import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter } from "@/design-system";

When to use

  • Detail views opened from a row in a list or table.
  • Filters, quick edits and contextual panels.

When not to use

  • A decision that must be answered before anything else — use a Dialog.
  • Content that simply did not fit in a dialog — that is a sign it needs its own page.

Sides and sizes

Right (default), left for navigation-style panels, bottom for mobile quick actions.

Account details drawer

A structured detail panel with identity, contextual actions, tabs, settings and nested records. The inset desktop treatment becomes full screen on mobile.

Searchable selection drawer

A wide, task-focused panel for searching, filtering and selecting records without losing the page underneath. On mobile it becomes full screen.

Drawer sizes and recommended compositions.
SizeCompositionTypical use
smCompact controls or filtersFilters, quick settings, mobile navigation
mdSingle-column contentRecord details, quick edits
lgLonger forms and grouped detailContextual workflows with several sections
xlSearch, filters and a responsive data tableRecord selection and dense comparison workflows

Accessibility

  • Modal by default: focus moves in, is trapped, and returns to the trigger on close.
  • The close IconButton carries a label; Escape closes unless dismissal is guarded.
  • The body scrolls while the header and the footer actions stay in place.

DetailDrawer

Show a record in full beside the list it was opened from, composed entirely from existing Card, List, Badge and KeyValue components.

import { DetailDrawer } from "@/design-system";

When to use

  • Inspecting a row without losing the list, its filters or its scroll position.

When not to use

  • Editing — open a FormDialog or a guarded drawer instead.

Resident details

Identity, contact, services and activity, with the edit action pinned to the footer.

Accessibility

  • Read-only, so it dismisses freely on Escape, outside click and the close button.
  • Status is a Badge with text, never a colour-only dot.

FilterDrawer

Narrow a result set from a panel beside it, with Clear all and Apply pinned to a sticky footer.

import { FilterDrawer } from "@/design-system";

When to use

  • More filters than a toolbar can hold.
  • Any filtering surface on tablet and mobile, where inline filters crowd the results.

When not to use

  • One or two filters — keep those inline in the table toolbar.

Filter residents

Status, service, assigned staff and date range, all reusing existing controls.

Accessibility

  • Every control is an existing labelled form component — no bespoke filter widgets.
  • The footer stays reachable at every viewport height; the criteria scroll independently.

Choosing the right surface

Dialog, Drawer, Toast and Banner are not interchangeable. Pick by how much the person must respond and how much page context they need to keep.

Surface selection.
SurfaceUse whenNever for
DialogA focused task or decision that needs an answer nowPassive information or success messages
DrawerA secondary workflow or contextual detail while the page stays in viewContent that only moved here because it did not fit
ToastTemporary feedback that needs no responseAnything the person must acknowledge
BannerPersistent page-level information or an ongoing conditionAn immediate decision

Dismissal is a data-safety decision

Read-only surfaces — detail drawers, informational dialogs — close on Escape, outside click and the close button, because nothing can be lost. Anything holding edits sets guardDismiss, which blocks Escape and outside click and routes the exit through the unsaved-changes guard. FormDialog does this by default, and also blocks dismissal while a submit is in flight so a slow network cannot produce two records.
Keyboard behaviour — identical across dialogs and drawers.
KeyBehaviour
Tab / Shift+TabCycles the focusable controls inside the surface; focus never escapes while modal.
EnterActivates the focused control; inside a form dialog, submits the form.
SpaceActivates buttons, checkboxes and switches.
EscapeCloses, unless dismissal is guarded — then the unsaved-changes guard opens.
On closeFocus returns to the element that opened the surface.
Responsive behaviour.
ViewportDialogDrawer
DesktopCentred, size-capped, 24px paddingRight panel at sm/md/lg/xl width
TabletWidth capped to the viewport minus 32pxPanel width capped; content reflows to one column
MobileBottom sheet, full width, body scrolls, footer actions stacked and full-widthFull-screen side sheet with header, close control and pinned footer

Do

  • Pick the dialog size from the content, using the four defined sizes.
  • Put field errors on fields and form-level errors in an Alert above them.
  • Keep header and footer fixed and let only the body scroll for long content.
  • Guard dismissal whenever unsaved edits exist, and confirm destructive actions.
  • Close the surface on success and confirm with a Toast.

Don't

  • Do not build a second overlay, backdrop or elevation treatment.
  • Do not invent widths, paddings or one-off modal styles.
  • Do not create dialog-specific form controls, buttons or error styles.
  • Do not replace the whole dialog with a spinner while submitting.
  • Do not use a dialog for a success message, or a drawer for a blocking decision.