4.0 KiB
Wails Dialogs (frontend, @wailsio/runtime)
The frontend dialog API lives in @wailsio/runtime as Dialogs. Authoritative signatures are in
frontend/node_modules/@wailsio/runtime/types/dialogs.d.ts.
See CLAUDE.md for project conventions on when to use these (errors vs. inline validation, confirmation flow, etc.).
Message dialogs
import { Dialogs } from "@wailsio/runtime";
await Dialogs.Info({ Title, Message, Buttons?, Detached? });
await Dialogs.Warning({ Title, Message, Buttons?, Detached? });
await Dialogs.Error({ Title, Message, Buttons?, Detached? });
await Dialogs.Question({ Title, Message, Buttons?, Detached? });
All four return Promise<string> resolving to the Label of the button the user clicked. With no Buttons provided you get a single OK button — the promise just resolves when the user dismisses.
MessageDialogOptions fields:
Title?: string— window title (short).Message?: string— the body text.Buttons?: Button[]— custom buttons. EachButtonis{ Label?, IsCancel?, IsDefault? }.IsCancelis what Esc/⌘. triggers;IsDefaultis what Enter triggers.Detached?: boolean— whentrue, the dialog isn't tied to the parent window (no sheet behavior on macOS).
File dialogs
Dialogs.OpenFile(options) and Dialogs.SaveFile(options) — see dialogs.d.ts for the full OpenFileDialogOptions / SaveFileDialogOptions field set (filters, ButtonText, multi-select, hidden files, alias resolution, directory mode, etc).
Per-OS behavior
| Platform | Behavior |
|---|---|
| macOS | Sheet-style when attached to a parent window. Up to ~4 custom buttons render naturally. Keyboard: Enter = default, ⌘. or Esc = cancel. Follows system theme. Accessibility is built-in. |
| Windows | Modal TaskDialog-style. Standard button labels are nudged toward OS conventions. Keyboard: Enter = default, Esc = cancel. Follows system theme. |
| Linux | GTK dialogs — appearance varies by desktop environment (GNOME/KDE). Follows desktop theme. Standard keyboard nav. |
Behavioural notes that affect us:
- The promise resolves with the button label string, not an index. Compare against the literal
Labelyou passed (e.g.if (result !== "Delete") return;). Buttons[]on Linux/Windows uses the labels you supply, but the OS layout/styling is fixed.Dialogs.Errorplays the platform error sound and uses the platform error icon. Don't use it for confirmations — useDialogs.WarningorDialogs.Question.- Don't fire dialogs in a tight loop or from every keystroke — they interrupt focus and (on macOS) animate in/out. Debounce or guard with a
busyflag.
Frameless / custom-window dialogs (Go side)
When the native dialog API isn't enough — rich content, embedded webview, multi-screen flow — open a regular Wails window. This is done on the Go side via app.Window.NewWithOptions(application.WebviewWindowOptions{...}). Useful options:
Parent— attach to a parent so OS treats it as a child.AlwaysOnTop: true— float above the parent.Frameless: true— no titlebar/chrome.Resizable: false(alsoDisableResize: truein v3) — fixed-size dialog feel.Hidden: trueinitially, thendialog.Show()+dialog.SetFocus().
We do use this pattern, but pragmatically: WindowManager.OpenSettings and OpenBrowserLogin are regular small webview windows (not modal sheets) with no resize, hidden minimise/maximise buttons, and a translucent macOS title bar. They're not classic "OS modal dialogs"; they're just lightweight ancillary windows that look the part. Modal behaviour (parent.SetEnabled(false)) is intentionally not used — the user can still click back to the main window.
In-app modals (NewProfileDialog, delete-profile confirmation, etc.) are Radix Dialog primitives inside the main webview. Reach for a custom OS window only when content must escape the main window (BrowserLogin is the canonical example — its lifecycle is tied to the SSO wait) or when the window needs its own taskbar entry / dock icon.