Dialogs & Overlays
One modal pattern for every “stop and decide” moment — publish, deactivate, quick-edit, acknowledge. It floats honestly: a sheet of glass on a hairline over a light ink wash, arriving with a single buttery move so the rep knows it’s now. The page behind stays sharp — no blurred spotlight, no bounce on data.
Where it belongs
A dialog is a Component, but it answers to two foundations at once. Borders & elevation already names it: overlays are the rare thing that genuinely floats, so they — and only they — earn the glass material’s deep soft shadow plus a hairline border. Motion already rules it too: “blur & dimmed spotlight scrims” sit on the don’t-animate list, so the page stays sharp behind a modal here, never frosted.
So the modal invents nothing. Its surface, border, radius and shadow come straight from the registry’s token set; its entrance and exit are the existing buttery decelerates (--ease-out in, --ease-in out); its body is built from chips, receipt rows and form fields you already have. This page just choreographs them into the one job a dialog does: ask for a single decision, then get out of the way.
The material — glass, adopted
The translucent floating-chrome surface is now the system, baked into the token registry so every page inherits it. The dialog floats, so it IS glass: a see-through warm surface, real blur, bright top hairline and a soft deep shadow — on floating chrome only. The page behind stays sharp; the dialog frosts just its own footprint over a light ink wash.
Translucent glass, system font, soft orange CTA — all from the foundation. The scrim is a flat --ink-900 wash at .34 opacity, no filter: the page reads through it, sharp.
Glass material tokens
The premium translucent surface for things that FLOAT over the page: dialogs, menus, popovers. The page behind stays sharp; only a floating surface frosts what is directly beneath it.
| Token | Value | Use |
|---|---|---|
--glass-bg | rgba(252,251,249,0.78) | Balanced — glassy yet soft over busy content |
--glass-bg-solid | rgba(252,251,249,0.92) | Denser, for tall / legible bodies |
--glass-blur | 50px | Backdrop blur radius |
--glass-saturate | 135% | Backdrop saturation boost |
--glass-border | rgba(255,255,255,0.6) | Bright glass edge |
--glass-hi | rgba(255,255,255,0.6) | Inset top highlight |
--glass-shadow | 0 1px 0 var(--glass-hi) inset, 0 16px 44px rgba(27,26,24,0.16), 0 2px 8px rgba(27,26,24,0.06) | Composite glass shadow — highlight + deep soft drop |
--glass-input-bg | rgba(255,255,255,0.62) | Frosted input resting on glass |
--glass-input-bg-focus | rgba(255,255,255,0.92) | Frosted input focused — denser |
--glass-input-line | rgba(27,26,24,0.12) | Hairline input edge on glass |
--glass-inset-line | rgba(27,26,24,0.07) | Hairline divider inside glass (receipt rows) |
--glass-inset-bg | rgba(255,255,255,0.16) | Inset well on glass (receipt card) |
Applied by .km-glass and by the dialog container itself — background --glass-bg, backdrop blur --glass-blur + saturate --glass-saturate, edge --glass-border, composite --glass-shadow. The receipt inset, hairline dividers and frosted fields draw from the same group, so the panel reads as one continuous sheet.
Dialog principles
Five rules that decide when a modal is the right tool, how it should look, and how it must behave. When in doubt: would an inline confirmation do the job without stealing the whole screen? If yes, don’t open a dialog.
Float honestly
A dialog is the one surface that truly floats, so it alone earns the glass material — translucent, frosting only what sits directly beneath its own footprint. The page behind stays sharp under a light ink wash — never blurred, never a dimmed spotlight. Sharpness is the house signal.
One question, one dialog
A modal asks for exactly one decision. The title is the question, the body is the plain receipt, the footer is the choice. If you need two unrelated decisions, that’s two moments — or a full page.
Safety scales with stakes
Destructive actions read in red and stay understated — never a loud fill. A destructive dialog won’t dismiss on a stray scrim click; it waits for an explicit choice. Esc always cancels, because cancelling is safe.
Motion explains arrival
The scrim establishes, then the dialog lands onto it — a short rise + fade that says “this is now”. Leaving is quicker and accelerates away, so dismissing never makes the rep wait.
Trap, then return
Focus moves into the dialog on open and cycles inside it; nothing behind is reachable. On close, focus returns to the control that opened it. The page scroll locks while it’s up.
Anatomy
One container, four parts, all optional except the body and at least one action. A status-tinted icon badge sets the tone at a glance; the footer is a quiet action bar with the primary choice on the right.
1 · Icon badge — 36px flat tinted tile — status hue + glyph sets the tone. Kin to chips and status marks, not a pane of glass. Optional.
2 · Title + sub — Title is the question (heavy, section size). Sub is one calm line.
3 · Close — Quiet ghost ✕, top-right. Drop it on dialogs that must be answered.
4 · Body — The plain receipt, an explainer, or frosted form fields. Scrolls if tall.
5 · Action bar — No opaque fill — the same sheet of glass, separated by a single hairline. Primary right, Cancel left of it.
Container glass material · hairline --glass-border · --radius-lg · --glass-shadow · max-height caps at the viewport and the body scrolls · widths sm 400 / md 520 / lg 680.
The four dialogs
Every modal in the Design Manager is one of these. They share the container and the motion; only the icon tone, the body and the footer verb change. Click any to open the real thing — Esc, the ✕, the scrim and the buttons all work.
Confirm a commit
The deliberate “are you sure?” before something goes live. Shows the plain receipt, then asks.
Destructive confirm
High-stakes, irreversible-feeling. Red but understated; won’t dismiss on a stray click.
Form modal
A focused quick edit without leaving the list — name and hours, save or cancel.
Informational
Explain a manual desk task or a system state. One acknowledge button, nothing to decide.
Tone by job — ok-green icon for a commit, red for destructive, neutral for a form, amber for a heads-up. The verb on the primary button always names the action (“Publish”, “Mark inactive”), never “OK”.
Motion choreography
The whole move is two beats and lands in well under a second. It exists to answer “where did this come from?” — the dialog rises onto the wash.
Each button sets the entrance, then opens the same dialog.
Beat 1 — scrim. The ink wash fades 0 → .34 with --ease-out; the page stays sharp beneath it. Beat 2 — dialog. It rises 12px + fades in over --dur-base / --ease-out, landing onto the wash. Exit reverses, quicker, on --ease-in. Driven by the Web Animations API so the move is GPU-composited and lands cleanly every time.
Entrance character
Three flavors, all buttery decelerates from the token set. Rise is the
default — most on-system, the same gesture as a chat bubble. Zoom scales up
from 96%, a touch more assertive. Settle borrows --ease-settle’s tiny overshoot, reserved-feeling, for a committed action
landing.
Backdrop & the no-blur rule
The most tempting “premium” move is the one the system bans. A backdrop-blur behind a modal is the current consumer-app signal — and it’s expensive, distracting, and wrong for a workbench. The sharp page keeps the rep’s twenty locations legible right up to the edge of the dialog.
Watch the text behind. Sharp = the list stays readable and the page feels present. Blurred = the page is “switched off”, which reads as heavier and slower — and it costs a compositor pass on every frame. The blur in this system lives on the dialog itself, frosting only its own footprint; the scrim never blurs the page.
Default & shipped: a flat --ink-900 wash at .34 opacity, no filter. The blurred-spotlight scrim is off-system — it sits on Motion’s “what we don’t animate” list and ships as no class at all. The legacy spec page kept an opt-in variant only to demonstrate why it’s off-tone; this system documents the ban instead.
Sizes
Three widths cover the whole tool. Pick the smallest that holds the decision comfortably — a confirm is small, a form is medium, a richer review is large. Height is never fixed: the body scrolls and the container caps at the viewport.
sm 400 single decision, short receipt · md 520 the default — forms, multi-line receipts · lg 680 a fuller review with several field rows. Beyond lg, it’s a page, not a dialog.
Focus & accessibility
A modal that traps the keyboard wrong is worse than no modal. The behaviors below ship in the Dialog component and aren’t optional.
Keyboard & focus
- On open, focus moves to the primary action (or the first field in a form). Try it — open any dialog and start tabbing.
- Tab / Shift+Tab cycle within the dialog only; nothing behind it is reachable.
- Esc always cancels — closing is the safe path, so it’s never blocked.
- On close, focus returns to the exact control that opened the dialog.
Semantics & dismissal
role="dialog"+aria-modal="true", labelled by the title and described by the body.- Clicking the scrim dismisses an ordinary dialog; a destructive one sets
dismissible=falseand ignores the stray click. - The page scroll-locks while a dialog is open, so the background can’t drift under it.
- Hit targets stay ≥ the system minimum; the ✕ is a real button with an
aria-label.
Drawer with parallax
The overlay family's side panel: the drawer slides in while the page concedes the space — a breath of scale, a 12px shift, a touch dimmer. Two honest layers, no blur. For dealer profiles, review detail, photo approval, notifications.
Drawer component · page recedes via .km-drawer-stage__page (scale 0.985, −12px, 0.85 opacity) · both layers ride --dur-page / --ease-in-out · Esc and
scrim dismiss.
Spatial depth
Opt-in for high-stakes confirms: while the glass is up, the page behind concedes a 2% breath of scale — the dialog reads as a layer above a physical surface, not a sticker on it.
[data-recede='true'] → scale(0.98) on --dur-base / --ease-out. Reserved for
moments where the layering metaphor earns its keep.
Reduced motion
Same law as everywhere else: honored on the product surface, one decision. Movement is removed, meaning is not — the dialog still appears, traps focus, and returns it.
- The product is strict. With the OS setting on, the dialog and its scrim simply appear and disappear — no rise, no fade, no scrim animation. The Dialog component gates the move before it starts; the focus trap and return still run.
- The demos here are the shipped component, so they honor the setting the same way — what you open on this page is exactly what the product does.
- End state is identical either way: the same sharp page, the same trapped focus, the same returned focus on close.
Dialogs & Overlays · built on the system’s existing tokens, components & motion. Float honestly; ask one question; get out of the way.