Design 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.

Shared overlay rules — applied by the components, not re-declared per screen.
ConcernRuleToken / mechanism
PositioningRadix anchored positioning with collision detection; contextual surfaces flip and shift to stay in the viewportcollisionPadding = 12px
Spacing24px padding for dialogs and drawers, 16px for popovers, 4px for menu padding--ds-space-* only
RadiusLarge radius on panels, small radius on menu rows--ds-radius-lg / --ds-radius-sm
ElevationModal panels use shadow-lg; floating surfaces use the overlay shadow--ds-shadow-lg / --overlay-shadow
BackdropOne scrim for dialogs and drawers; contextual surfaces never dim the page--overlay-backdrop
AnimationFade for backdrops, scale for dialogs, slide for drawers--ds-duration-* / --ds-ease-*
FocusFocus moves in, is trapped while modal, returns to the trigger on closeRadix focus scope
LayeringCentralised z-index scale; no component writes a numeric z-index--ds-z-*
DismissEscape and close button always; outside click only where data cannot be lostguardDismiss 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.

V2 layering scale — use the utility, never a raw z-index.
LayerTokenUtilityUsed by
Base content--ds-z-basez-basePage content
Raised--ds-z-raisedz-raisedSticky table headers, overlapping controls
Sticky--ds-z-stickyz-stickySticky page headers, action bars
Dropdown--ds-z-dropdownz-dropdownDropdown Menu, Context Menu, Select, Combobox
Popover--ds-z-popoverz-popoverPopover, Hover Card
Tooltip--ds-z-tooltipz-tooltipTooltip
Drawer--ds-z-drawerz-drawerDrawer / side sheet and its backdrop
Dialog--ds-z-dialogz-dialogDialog, Alert Dialog and their backdrop
Command--ds-z-commandz-commandCommand / search overlay
Toast--ds-z-toastz-toastToast 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.

Accessibility

  • Rendered as role=dialog with aria-modal; the title is the accessible name and the description the accessible description.
  • Focus moves into the panel on open, stays trapped while open, and returns to the trigger on close.
  • Escape closes a dismissible dialog; a form dialog with guardDismiss keeps the user in place and requires an explicit action.
  • The close control is an IconButton with a visible focus ring and an accessible name.

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.

Accessibility

  • role=alertdialog: the title and description are announced immediately on open.
  • Focus lands inside the dialog and cannot leave it; Escape maps to the safe action.
  • The destructive action states the verb and object ('Delete resident') and carries a tone icon, so it never depends on the red alone.
  • In the unsaved-changes guard, the cancel action keeps the user editing, so an accidental dismissal never discards work.

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.

Accessibility

  • Same modal semantics as Dialog: focus trap, Escape to close, focus returned to the trigger.
  • The title names the sheet; the description explains what it contains.
  • Content scrolls inside the sheet, so the footer actions stay reachable on short viewports.
  • Below the sm breakpoint, left and right sheets expand to full screen so nothing clips.

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.

Accessibility

  • Trigger and content are wired with aria-expanded and aria-controls by Radix.
  • Focus moves into the popover; Escape and outside click close it and return focus to the trigger.
  • The surface flips and shifts to stay inside the viewport, with 12px collision padding.
  • Soft Glass keeps a visible border and full-contrast text over any background.

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.

Accessibility

  • Shows on hover and on keyboard focus, and dismisses on Escape.
  • Never the only source of an accessible name — icon-only controls also carry a label prop.
  • Rendered above other overlays at the tooltip layer, so it is never clipped by a menu.
  • Inverse surface keeps text contrast well above the AA threshold in both themes.

Choosing between overlays

These patterns must never become visually interchangeable — each one signals a different level of interruption.

Overlay differentiation.
PatternRoleInterrupts the page?Typical Centrim Life use
DialogRequires focused attentionYes — modal with a scrimEdit resident information
Confirmation dialogAcknowledge a consequenceYes — alert dialogDelete resident, cancel a recurring job
DrawerExtra context or a side workflowYes, but the page stays visiblePreview a resident record from the list
PopoverContextual information or controlsNoFilter records
Dropdown menuActions and commandsNoTable row actions
Context menuAccelerator for the same commandsNoRight-click a resident row
SelectChoose a valueNoChoosing a service in a form
TooltipBrief supplementary textNoNaming an icon-only button
Command overlayFast navigation and discoveryYes — keyboard-firstSearch across the application

Dismiss, focus, motion & responsive behaviour

Behaviour matrix applied by the components themselves.
ComponentDismissFocusMotionMobile
DialogClose button, Escape, outside click (blocked with guardDismiss)Trapped; returned to triggerBackdrop fade + panel scaleBottom sheet, full width
Confirmation dialogsClose via an explicit action or Escape (safe action)Trapped; returned to triggerBackdrop fade + panel scaleBottom sheet, stacked buttons
DrawerClose button, Escape, outside click (blocked with guardDismiss)Trapped; returned to triggerSlide from the anchored edgeFull-screen sheet for left/right
PopoverEscape, outside click, selecting an actionMoves in; returned to triggerFade + scaleRepositions inside the viewport
Dropdown / context menuEscape, outside click, choosing an itemRoving focus; returned to triggerFade + scaleConstrained width, 44px touch rows
TooltipEscape, blur, pointer leaveNever takes focusFadeSuppressed on touch; label stays on the control
Command overlayEscape, outside click, running a commandInput holds focus; trappedBackdrop fade + panel scaleNear 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.