Skip to content

Dialogs

components specs/components/dialogs.kmd

Modal interruption surface — basic dialog (centered modal), full-screen dialog (mobile/onboarding), and alert dialog (destructive confirm). Material parity (`/components/dialogs`). Includes scrim, focus trap, ESC handling, and accessibility contract.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Dialogs

Facet Visual do Koder Design. Material parity: https://m3.material.io/components/dialogs.

3 dialog variants

VariantVisualUse
BasicCentered modal, max-width 560 px, surface bg, elevation 5Standard confirm / settings dialog
Full-screenEdge-to-edge surface, app bar with close buttonMobile multi-step flow, immersive picker
AlertBasic dialog with destructive icon + 2 buttons (Cancel + destructive)Destructive confirmation

Anatomy (basic)

┌──────────────────────────────────┐
│ [icon?] Title                    │   ← title row
│                                  │
│ Body content — explanation       │   ← body
│ of the decision the user faces.  │
│                                  │
│              [Cancel] [Action]   │   ← actions, right-aligned
└──────────────────────────────────┘
         Scrim overlay (page bg darkened)
  • Container: radius-lg (16 px default), surface bg, elevation 5
  • Padding: 24 px all sides
  • Icon (optional): 24 px above title, accent color
  • Title: headline-small (24/32, weight 600)
  • Body: body-medium (14/20)
  • Actions: button row, right-aligned (or left in RTL), 8 px gap

R1 — Scrim

The semi-transparent overlay behind the dialog.

  • Background: rgba(0, 0, 0, 0.5) (light mode) or rgba(0, 0, 0, 0.7) (dark)
  • Click on scrim: dismisses dialog (unless nonDismissible: true)
  • ESC key: dismisses dialog (unless nonDismissible: true)
  • Click on scrim is equivalent to clicking "Cancel" action

Per interaction/states.kmd: animate scrim fade-in (medium duration, decelerate easing) and dialog scale 0.95→1.0 in parallel.

R2 — Full-screen variant

Used on Compact class (per window-size-classes.kmd) or for multi-step flows.

┌──────────────────────────────────┐
│ [X]   Title                  [✓] │   ← app bar w/ close + confirm
├──────────────────────────────────┤
│                                  │
│  Content fills viewport          │
│                                  │
│                                  │
└──────────────────────────────────┘
  • App bar at top with close button (left) + confirm action (right)
  • Content fills below app bar
  • No scrim (it IS the viewport)
  • Back navigation per navigation/back-behavior.kmd (back button = dismiss)

R3 — Alert dialog (destructive)

Basic dialog variant specifically for destructive confirms.

  • Icon: warning or error glyph in error color
  • Title: question form ("Delete 12 messages?")
  • Body: consequences ("This cannot be undone.")
  • Actions: Cancel (text variant, default focus) + Delete (text or outlined w/ error color)
  • Destructive action is RIGHT-aligned (Material convention; some guidelines prefer left — Koder follows right)

Default focus: Cancel button (less destructive); Delete requires deliberate tab/click. Prevents accidental confirm on keyboard "Enter".

R4 — Focus trap

When dialog is open:

  • Focus moves into the dialog (first focusable element OR explicitly designated)
  • Tab cycles within dialog only — can't tab to background
  • Shift+Tab cycles backward within dialog
  • On dismiss: focus returns to the element that opened the dialog

Per koder_kit (Flutter): Dialog widget handles this automatically. Per koder_web_kit: needs explicit focus trap implementation.

R5 — Lifecycle hooks

PhaseEvent
Pre-openonWillOpen — can cancel by returning false
Open animation startonOpening
Open animation endonOpened — first focus applied
Action clickedonAction(actionId) — return true to dismiss; false to keep open
Pre-dismissonWillClose — can cancel
Close animation startonClosing
Close animation endonClosed — focus returned to opener

R6 — Title + body content rules

Per foundations/ux-writing.kmd:

  • Title: question form for confirms, statement for info ("Saved.")
  • Body: max 200 chars typically; explain the WHY or consequences
  • Avoid jargon, avoid hedging ("might", "could possibly")
  • Use direct voice ("Your changes are saved.")

R7 — Forbidden patterns

  • ❌ Dialog with > 3 actions (use sheet/bottom-sheet instead)
  • ❌ Nested dialogs (one at a time — second one replaces first)
  • ❌ Auto-dismissing dialog after timer (use snackbar)
  • ❌ Modal for non-critical info (use snackbar or tooltip)
  • ❌ Dialog with form > 1 short input (use full-screen variant or dedicated page)
  • ❌ Disabling close button as the ONLY exit (always provide Cancel/X)

R8 — Accessibility

  • role="dialog" + aria-modal="true"
  • aria-labelledby pointing to title element id
  • aria-describedby pointing to body element id (optional but helpful)
  • Title is <h2> semantic
  • ESC key handler bound
  • Focus visible on initial focus + on all interactive elements
  • Screen reader announces title + body on open

R9 — Per-preset variation

PresetTreatment
material3Elevation 5 shadow + radius-lg
windows_11Mica backdrop instead of plain bg
windows_953D bevel border + sharp corners + system title bar
ios_cupertinoradius-xl (20 px) + ultra-light blur scrim
glassmorphismTranslucent surface + backdrop blur
brutalistSharp corners + thick border + no shadow
terminal_classicText-only with ASCII box drawing
  • foundations/ux-writing.kmd — title + body voice
  • navigation/back-behavior.kmd — back/ESC dismissal
  • themes/motion.kmd — entry/exit transitions
  • themes/elevation.kmd — level 5 default
  • errors/user-facing-messages.kmd — error dialog content

References