# Dialogs
`win.dialogs()` and `wm.dialogs()` are the promise-based `alert` / `confirm` / `prompt` factory built into the SGApps window-manager, plus a `customPopup` escape hatch for dialogs that don't fit those three shapes. Every dialog is a real WM window — parented to the caller (for `win.dialogs()`) or floating at the manager root (for `wm.dialogs()`) — so it inherits the same focus cascade, close cascade, chrome, and styling as any other app window.
The API contract mirrors `electron.dialog.showMessageBox(browserWindow, opts)`, so an Electron adapter can later swap the DOM path for native OS dialogs without call-site changes.
Related: [Window Manager](window-manager.md) covers how to obtain a `wm`; [Window](window.md) covers the `win` object.
## Try it live
The buttons below drive a real window-manager loaded via `exporter.js` on this page — the same `wm` instance the code snippets use. First click boots the SDK (~1 s); subsequent clicks reuse it.
**Alerts:**
**Confirms:**
Result appears here…
**Prompts:**
Result appears here…
**Custom popups:**
Result appears here…
## Two entry points
```js
// Parented to a specific window — closes when that window closes,
// raises on top when that window is focused
this.window().dialogs().alert("File saved");
// Rooted at the manager — no owning parent, no cascade.
// Use for global flows (session expired, logout confirm, app-level errors).
wm.dialogs().alert("Session expired", {
icon: "paper/icons/status/dialog-warning"
});
```
The two entry points return an object with the same four methods. `alert` / `confirm` / `prompt` share the same promise-based contract; `customPopup` is the escape hatch for dialogs that don't fit those three shapes (see [customPopup](#custompopup-opts) below). Only the parent binding differs.
## Methods
`alert` / `confirm` / `prompt` each return `Promise<{ window, result }>`:
- `window` — the dialog's `WindowApplication` instance (already closed by the time the promise resolves; useful only for logging or subclass extensions)
- `result` — the user's answer; type depends on the method
`customPopup` returns a **context object synchronously** instead of a promise — see its own section for the contract.
### `alert(message, opts?)`
One message, one OK button. Escape and Enter both resolve with `undefined`.
```js
win.dialogs().alert("Backup complete.").then(function (r) {
// r.result === undefined
});
```
### `confirm(message, opts?)`
One message, Yes/No buttons. Enter → `true`, Escape → `false`. Pass `opts.destructive: true` to turn the Yes button red.
```js
win.dialogs().confirm("Delete file?", {
destructive: true,
yesLabel: "Delete",
noLabel: "Cancel"
}).then(function (r) {
if (r.result) doDelete();
});
```
### `prompt(message, defaultValue?, opts?)`
One message, one input, OK/Cancel. Enter (single-line) or Ctrl/Cmd+Enter (multiline) submits the value. Escape resolves `null` — matches native `window.prompt`.
```js
win.dialogs().prompt("New file name:", "untitled.txt", {
placeholder: "name.ext"
}).then(function (r) {
if (r.result !== null) rename(r.result);
});
// Multiline
win.dialogs().prompt("Describe the change:", "", {
multiline: true,
rows: 5,
placeholder: "what changed…"
}).then(function (r) {
if (r.result !== null) commitWithMessage(r.result);
});
```
### `.prompt(message, opts)` — signature tolerance
If the second argument is an options object (not a string default), it's treated as `opts` and `opts.defaultValue` fills in:
```js
win.dialogs().prompt("Your name?", { placeholder: "type here…" });
// same as .prompt("Your name?", undefined, { placeholder: "type here…" })
```
### `customPopup(opts?)`
Escape hatch for dialogs that don't fit `alert` / `confirm` / `prompt`. Opens a window pre-wired with all the shared plumbing (parent-child focus / close cascade, ESC → resolve, default geometry centred on parent), then hands back a **context object** so the caller owns the body.
**Contract differences vs the other three methods**
| | `alert / confirm / prompt` | `customPopup` |
|---|---|---|
| **Returns** | `Promise<{window, result}>` | `{ dlg, promise, resolve, container, focusFirst }` synchronously |
| **Message arg** | Required (string / HTMLElement / `{html}`) | None — you populate `container` yourself |
| **Buttons** | Built-in (OK / Yes-No / OK-Cancel) | None — you add whatever you want |
| **ESC → resolve** | Method-specific default (`undefined` / `false` / `null`) | `opts.escResult`, defaults to `undefined` |
| **Sizing** | JS defaults 380×200 (opts.width/height override); body scrolls when content overflows | JS defaults 380×200 (opts.width/height override); caller owns the body, so overflow behaviour is whatever they build |
**Returned context**
| Property | Type | Purpose |
|---|---|---|
| `dlg` | `WindowApplication` | The dialog window handle — same surface as any WM window. Getter/setter accessors `dlg.title(str?)`, `dlg.width(px?)`, `dlg.height(px?)`, `dlg.left(px?)`, `dlg.top(px?)`, `dlg.icon(id?)`; lifecycle `dlg.close(force)`, `dlg.focus()`, `dlg.maximize / minimize / restore()`; events `dlg.on(evt, fn)` / `dlg.once` / `dlg.off`; DOM `dlg.container()` / `dlg.node()`; class helpers `dlg.addClass / removeClass`. Note there is no `dlg.resize(w, h)` — set width and height with the individual accessors. |
| `promise` | `Promise<{window, result}>` | Resolves when `resolve(v)` is called or the dialog closes by another path (ESC, parent cascade, external close). On non-resolve close, `result === opts.escResult` |
| `resolve(value)` | `function` | Close the dialog and resolve `promise` with `value`. Idempotent — safe to bind to multiple events |
| `container` | `HTMLElement` | The dialog's inner container. Cleared before return — populate with any DOM (buttons, inputs, canvas, iframe, …) |
| `focusFirst()` | `function` | Focus the first `button.is-primary` / `button` / `input` / `textarea` inside `container`. Call it after populating |
**Example — colour picker**
```js
var ctx = win.dialogs().customPopup({
title: "Pick a colour",
width: 320, height: 180,
escResult: null // ESC → cancelled
});
var input = document.createElement("input");
input.type = "color";
input.value = "#a6bb11";
ctx.container.appendChild(input);
input.addEventListener("change", function () {
ctx.resolve(input.value); // close + settle with hex
});
ctx.focusFirst();
ctx.promise.then(function (r) {
if (r.result !== null) applyBrandColour(r.result);
});
```
**When to reach for `customPopup`**
- The dialog has more than one input, or an input the built-in `prompt` doesn't cover (radio group, colour, file, date range, canvas signature pad, …).
- The dialog is a multi-step wizard whose result depends on which "Next" branch the user takes.
- You need an embedded iframe, video, or third-party widget as the dialog body.
- You want a dialog whose body updates over time (progress bar, live log stream) and closes on a specific event rather than an OK click.
**When NOT to reach for `customPopup`**
- A single message + OK / Yes-No / one input covers your case — `alert` / `confirm` / `prompt` are shorter, keyboard-mapped by default, and match the Electron adapter surface.
- You just need rich HTML content inside a standard OK dialog — pass an `HTMLElement` or `{html: "..."}` as the message to `alert` / `confirm` and skip the manual wiring.
**Scrolling — `scrollable: true`**
The dialog window's inner `` has `overflow: hidden` by default. That is intentional for the standard shell: `.windowManager--dialog-body` owns the scroll region, buttons stay pinned to the bottom via a flex column, and hiding the section prevents phantom scrollbars from 1-pixel rounding drift.
When you build a **custom** body that does **not** follow that shell (`.windowManager--dialog` > `.windowManager--dialog-body` + `.windowManager--dialog-buttons`), the section-level `overflow: hidden` clips anything that overruns the viewport — on small screens the operator can't reach content past the bottom of the section. Pass `scrollable: true` to opt out.
```js
// Long form that doesn't use the standard shell — let the section scroll.
var ctx = win.dialogs().customPopup({
title: "Edit user",
width: 560, height: 480,
escResult: "cancel",
scrollable: true // <-- section can scroll now
});
// Fill ctx.container directly, no .dialog-body wrapper needed.
```
Rule of thumb:
- **Standard shell** (you mount `.windowManager--dialog` + `.dialog-body` + `.dialog-buttons`): leave `scrollable` **off**. The body scrolls in place while buttons stay pinned.
- **Free-form container** (you paint straight into `ctx.container` with no body/button separation): turn `scrollable` **on**. Otherwise a body taller than the viewport is unreachable.
Under the hood the option toggles the `windowManager--window-class--dialog-scroll` class on the dialog window; the CSS rule that reacts to it lives next to the base dialog section rule in `window-manager/style.css`.
## Content — string, HTMLElement, or `{html}`
The first argument accepts three shapes. Everything else in the API is unchanged.
### 1. Plain text
Pre-wrap + word-break so long content stays readable at any dialog width.
```js
win.dialogs().alert("File saved successfully.");
```
### 2. HTMLElement
Appended directly. You control the structure; the WM stylesheet provides sensible defaults for `h1-h4`, `p`, `ul/ol`, `code`, `pre`, `table`, `a`, `hr`, and media elements.
```js
var el = document.createElement("div");
el.innerHTML =
'
Backup done
' +
'
' +
'
database — 12.4 MB
' +
'
media/ — 380 files
' +
'
';
win.dialogs().alert(el, { title: "Backup complete" });
```
### 3. `{ html: "..." }` shortcut
For static HTML you'd otherwise stringify. Semantically identical to option 2 but skips the `createElement + innerHTML` boilerplate.
```js
win.dialogs().confirm({
html:
'
Not undoable.
' +
'
Revoke access for 2 devices and 5 sessions?
'
}, { title: "Confirm revoke", destructive: true, yesLabel: "Revoke" });
```
## Options
Everything is optional. Only sensible defaults apply when omitted.
```js
opts = {
title, // window title (defaults: "Notification" / "Confirm" / "Input" / "Dialog")
icon, // e.g. "paper/icons/status/dialog-warning"
okLabel, // default "OK" — alert / prompt
cancelLabel, // default "Cancel" — prompt
yesLabel, // default "Yes" — confirm (also accepts okLabel)
noLabel, // default "No" — confirm (also accepts cancelLabel)
details, // string | HTMLElement | {html} — collapsible info block under the message
destructive, // confirm only — Yes button turns red ("is-danger" style)
multiline, // prompt only —