Centrim Design System — a token-first, accessible React component system.
Advanced overlays & layered interactions
The V2 overlay foundation: one set of positioning, elevation, backdrop, motion, focus and layering rules shared by Dialog, Drawer, Popover, Dropdown Menu, Context Menu, Tooltip and the Command overlay.
What is this?
The V2 overlay family — Dialog, confirmation dialogs, Drawer, Popover, Dropdown Menu, Context Menu, Tooltip and the Command overlay — all built on a single overlay foundation and a centralised layering scale.
When to use it
- A task, decision or detail has to appear above the current page.
- A control needs contextual information, commands or a compact form.
- The user needs to search or navigate quickly from the keyboard.
How to use it
- Pick the component by behaviour, using the differentiation table below.
- Compose the existing Button, Input, Badge and feedback components inside the overlay.
- Use the z-* layering utilities; never write a numeric z-index.
What to avoid
- Do not build a one-off modal, menu or tooltip surface.
- Do not apply glass to modal dialogs, drawers holding forms, or cards.
- Do not allow outside-click dismissal where unsaved data could be lost.
Overlay foundation
Every layered surface in V2 is built on one foundation. Components differ in behaviour and placement, never in visual language.
| Concern | Rule | Token / mechanism |
|---|---|---|
| Positioning | Radix anchored positioning with collision detection; contextual surfaces flip and shift to stay in the viewport | collisionPadding = 12px |
| Spacing | 24px padding for dialogs and drawers, 16px for popovers, 4px for menu padding | --ds-space-* only |
| Radius | Large radius on panels, small radius on menu rows | --ds-radius-lg / --ds-radius-sm |
| Elevation | Modal panels use shadow-lg; floating surfaces use the overlay shadow | --ds-shadow-lg / --overlay-shadow |
| Backdrop | One scrim for dialogs and drawers; contextual surfaces never dim the page | --overlay-backdrop |
| Animation | Fade for backdrops, scale for dialogs, slide for drawers | --ds-duration-* / --ds-ease-* |
| Focus | Focus moves in, is trapped while modal, returns to the trigger on close | Radix focus scope |
| Layering | Centralised z-index scale; no component writes a numeric z-index | --ds-z-* |
| Dismiss | Escape and close button always; outside click only where data cannot be lost | guardDismiss prop |
One foundation, many behaviours
The overlay family shares a single surface language so a user reads depth the same way everywhere: modal panels are opaque and sit on a scrim; floating contextual surfaces use the Soft Glass treatment and never dim the page. Nothing here is per-component styling — a new overlay composes the existing primitives and inherits all of it.
Glass is deliberately restricted to short-lived floating surfaces (Popover, menus, command overlay, hover card). Dialogs, drawers holding forms, and cards stay opaque because reading and data entry beat visual effect. Where backdrop-filter is unsupported the same tokens paint a solid panel instead of an unreadable transparent one.
Layering & z-index
A single stacking order defined in tokens. Utilities are generated from those tokens, so stacking bugs are fixed in one file.
| Layer | Token | Utility | Used by |
|---|---|---|---|
| Base content | --ds-z-base | z-base | Page content |
| Raised | --ds-z-raised | z-raised | Sticky table headers, overlapping controls |
| Sticky | --ds-z-sticky | z-sticky | Sticky page headers, action bars |
| Dropdown | --ds-z-dropdown | z-dropdown | Dropdown Menu, Context Menu, Select, Combobox |
| Popover | --ds-z-popover | z-popover | Popover, Hover Card |
| Tooltip | --ds-z-tooltip | z-tooltip | Tooltip |
| Drawer | --ds-z-drawer | z-drawer | Drawer / side sheet and its backdrop |
| Dialog | --ds-z-dialog | z-dialog | Dialog, Alert Dialog and their backdrop |
| Command | --ds-z-command | z-command | Command / search overlay |
| Toast | --ds-z-toast | z-toast | Toast viewport — always the top layer |
Components
Dialog
A modal panel for a task that needs the user's full attention — editing a record, completing a short form, or reading a decision-critical summary.
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/design-system";
When to use
- Editing resident information without losing the list behind it.
- A short, self-contained form such as assigning a service.
- A decision the user must resolve before continuing.
When not to use
- For supplementary context that does not block the page — use a Drawer or Popover.
- For a menu of actions — use a Dropdown Menu.
- For success or error feedback — use a Toast, Alert or Banner.
Sizes
Small for a single decision, medium as the default, large for richer content, full for a dense working surface.
Form dialog with dismissal guard
Edit resident information. Outside click and Escape are blocked so in-progress edits cannot be lost by a stray click.
Informational dialog
Read-only detail lifted out of a table row; dismissible by Escape, outside click or the close button.
Confirmation dialogs
Three standardised confirmations built on one alert-dialog surface: neutral confirmation, destructive confirmation and the unsaved-changes exit guard.
import { ConfirmDialog, DestructiveConfirmDialog, UnsavedChangesDialog } from "@/design-system";
When to use
- An action with a consequence the user should acknowledge before it runs.
- Deleting a resident, removing a service or cancelling a recurring job.
- Leaving a form that still holds unsaved edits.
When not to use
- For routine, easily reversible actions — confirm with a Toast and an Undo instead.
- For collecting extra input — use a form Dialog.
- For every save; confirmation fatigue makes the real warnings invisible.
Confirmation
Reversible but consequential — the confirm button carries the verb.
Destructive confirmation
Irreversible loss. Destructive tone icon, destructive semantic colour and an explicit verb — no new red is introduced.
Unsaved changes
Three-way exit guard. 'Keep editing' is the cancel action, so dismissing the dialog is always the safe outcome.
Drawer / side sheet
A panel anchored to an edge of the screen for extra detail or a side workflow, while the page underneath keeps its context.
import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter } from "@/design-system";
When to use
- Previewing a resident record from the list without navigating away.
- A filter panel or a longer side workflow with several controls.
- Content taller than a dialog can comfortably hold.
When not to use
- For a decision that must be resolved first — use a Dialog.
- For two or three quick controls — use a Popover.
- As a substitute for a full page when the workflow is long and multi-step.
Sides
Right is the default for record detail; bottom suits mobile-first pickers.
Guarded workflow sheet
A side sheet holding edits blocks outside-click dismissal, exactly like a form dialog.
Popover
A small floating surface anchored to its trigger, holding contextual information, a couple of controls or a compact filter.
import { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverTitle, PopoverFooter } from "@/design-system";
When to use
- A filter with two or three controls above a records table.
- Extra context about a field that is too long for a tooltip.
- Quick actions attached to a specific element.
When not to use
- For a list of commands — use a Dropdown Menu.
- For a full form — use a Dialog or Drawer.
- For a one-line hint — use a Tooltip.
Placement
Top, bottom, left and right; the popover repositions itself near a viewport edge.
Filter records
A compact filter reusing the existing Input and Button components.
Tooltip
A brief, non-interactive label that explains a control — most often an icon-only button or a collapsed navigation item.
import { TooltipProvider, Tooltip } from "@/design-system";
When to use
- Naming an icon-only IconButton in a table row or toolbar.
- Revealing labels in a collapsed sidebar rail.
- A short clarification of a field label or a metric.
When not to use
- For information the user must have — put it on the page.
- For anything interactive: links, buttons or form controls belong in a Popover.
- For long copy; keep it to a single short sentence.
Placement
Top, bottom, left and right.
Icon-only control
The tooltip repeats the accessible name the IconButton already carries.
Command / search overlay
A keyboard-first surface for searching across the application and jumping straight to a record, a service or a command.
import { CommandMenuDialog, CommandMenu, CommandMenuInput, CommandMenuList, CommandMenuGroup, CommandMenuItem } from "@/design-system";
When to use
- Global search across residents, services and jobs.
- Fast navigation for users who work in the product all day.
- Reaching commands that are otherwise buried in nested menus.
When not to use
- As the only path to an action — every command also has a visible control.
- For filtering a single table — use the SearchInput and Popover filters in place.
Search across the application
Categories, recent items, keyboard navigation and a no-results state — all from existing components.
Choosing between overlays
These patterns must never become visually interchangeable — each one signals a different level of interruption.
| Pattern | Role | Interrupts the page? | Typical Centrim Life use |
|---|---|---|---|
| Dialog | Requires focused attention | Yes — modal with a scrim | Edit resident information |
| Confirmation dialog | Acknowledge a consequence | Yes — alert dialog | Delete resident, cancel a recurring job |
| Drawer | Extra context or a side workflow | Yes, but the page stays visible | Preview a resident record from the list |
| Popover | Contextual information or controls | No | Filter records |
| Dropdown menu | Actions and commands | No | Table row actions |
| Context menu | Accelerator for the same commands | No | Right-click a resident row |
| Select | Choose a value | No | Choosing a service in a form |
| Tooltip | Brief supplementary text | No | Naming an icon-only button |
| Command overlay | Fast navigation and discovery | Yes — keyboard-first | Search across the application |
Dismiss, focus, motion & responsive behaviour
| Component | Dismiss | Focus | Motion | Mobile |
|---|---|---|---|---|
| Dialog | Close button, Escape, outside click (blocked with guardDismiss) | Trapped; returned to trigger | Backdrop fade + panel scale | Bottom sheet, full width |
| Confirmation dialogs | Close via an explicit action or Escape (safe action) | Trapped; returned to trigger | Backdrop fade + panel scale | Bottom sheet, stacked buttons |
| Drawer | Close button, Escape, outside click (blocked with guardDismiss) | Trapped; returned to trigger | Slide from the anchored edge | Full-screen sheet for left/right |
| Popover | Escape, outside click, selecting an action | Moves in; returned to trigger | Fade + scale | Repositions inside the viewport |
| Dropdown / context menu | Escape, outside click, choosing an item | Roving focus; returned to trigger | Fade + scale | Constrained width, 44px touch rows |
| Tooltip | Escape, blur, pointer leave | Never takes focus | Fade | Suppressed on touch; label stays on the control |
| Command overlay | Escape, outside click, running a command | Input holds focus; trapped | Backdrop fade + panel scale | Near full width, results scroll |
Rules
Do
- Choose the overlay by how much it should interrupt the user, then compose existing V2 components inside it.
- Use the z-* layering utilities generated from the --ds-z-* tokens for anything that stacks.
- Guard dismissal on any dialog or drawer that holds unsaved edits, and pair it with the unsaved-changes dialog.
- Keep glass for floating contextual surfaces, and keep modal panels opaque and readable.
- State the consequence in the title and put the verb on the confirm button.
Don't
- Do not create a new modal, tooltip, menu or backdrop style for a single screen.
- Do not use a numeric z-index or an arbitrary spacing value to fix a stacking or overlap bug.
- Do not introduce a new red for destructive actions — the semantic error tokens already exist.
- Do not rely on colour alone to mark a destructive item; keep the icon and the explicit verb.
- Do not put essential information, links or controls inside a tooltip.
Why it is designed this way
Overlays are where systems usually fragment: every screen invents its own modal, its own menu and its own z-index. V2 removes that option. Positioning, elevation, backdrop, motion, focus and layering live in the foundation, so a new overlay is a composition decision rather than a styling one, and a stacking bug is fixed once in the token file.
Dismissal is treated as a data-safety concern, not a convenience. Read-only overlays close on any gesture; anything holding edits blocks outside click and Escape and routes the exit through the unsaved-changes guard, where the safest option is the one a stray Escape triggers.