Centrim Design System — a token-first, accessible React component 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.
| Size | Max width | Use for |
|---|---|---|
| sm | max-w-sm | One decision or a single field. |
| md (default) | max-w-lg | A short form or focused summary. |
| lg | max-w-2xl | A multi-section form. |
| xl | min(64rem, viewport − 32px) | Dense forms, side-by-side fields, embedded tables. |
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.
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.
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.
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.
| State | Where it appears | Behaviour |
|---|---|---|
| Field error | Under the field, from FormField | aria-invalid on the control, message wired with aria-describedby. |
| Form / server error | Alert above the fields | Persists until the next submit; never replaces the form. |
| Submitting | Submit button spinner | Cancel, close and dismissal blocked; entered values preserved. |
| Success | Toast after close | The dialog closes; a Toast confirms, never a second dialog. |
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.
| Size | Composition | Typical use |
|---|---|---|
| sm | Compact controls or filters | Filters, quick settings, mobile navigation |
| md | Single-column content | Record details, quick edits |
| lg | Longer forms and grouped detail | Contextual workflows with several sections |
| xl | Search, filters and a responsive data table | Record selection and dense comparison workflows |
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.
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.
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 | Use when | Never for |
|---|---|---|
| Dialog | A focused task or decision that needs an answer now | Passive information or success messages |
| Drawer | A secondary workflow or contextual detail while the page stays in view | Content that only moved here because it did not fit |
| Toast | Temporary feedback that needs no response | Anything the person must acknowledge |
| Banner | Persistent page-level information or an ongoing condition | An immediate decision |
Dismissal is a data-safety decision
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.| Key | Behaviour |
|---|---|
| Tab / Shift+Tab | Cycles the focusable controls inside the surface; focus never escapes while modal. |
| Enter | Activates the focused control; inside a form dialog, submits the form. |
| Space | Activates buttons, checkboxes and switches. |
| Escape | Closes, unless dismissal is guarded — then the unsaved-changes guard opens. |
| On close | Focus returns to the element that opened the surface. |
| Viewport | Dialog | Drawer |
|---|---|---|
| Desktop | Centred, size-capped, 24px padding | Right panel at sm/md/lg/xl width |
| Tablet | Width capped to the viewport minus 32px | Panel width capped; content reflows to one column |
| Mobile | Bottom sheet, full width, body scrolls, footer actions stacked and full-width | Full-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.