Message dialogs
Standard info, warning, error dialogs.
Create custom dialog windows using regular Wails windows with dialog-like behaviour. Build custom forms, complex input validation, branded appearance, and rich content (images, videos) whilst maintaining familiar dialog patterns.
// Create custom dialog windowdialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Custom dialog", Width: 400, Height: 300, AlwaysOnTop: true, Frameless: true, Hidden: true,})
// Load custom UIdialog.SetURL("http://wails.localhost/dialog.html")
// Show as modaldialog.Show()dialog.SetFocus()That’s it! Custom UI with dialog behaviour.
type Customdialog struct { window *application.WebviewWindow result chan string}
func NewCustomdialog(app *application.Application) *Customdialog { dialog := &Customdialog{ result: make(chan string, 1), }
dialog.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Custom dialog", Width: 400, Height: 300, AlwaysOnTop: true, Resizable: false, Hidden: true, })
return dialog}
func (d *Customdialog) Show() string { d.window.Show() d.window.SetFocus()
// Wait for result return <-d.result}
func (d *Customdialog) Close(result string) { d.result <- result d.window.Close()}func ShowModaldialog(parent *application.WebviewWindow, title string) string { // Create dialog dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 200, Parent: parent, AlwaysOnTop: true, Resizable: false, })
// Disable parent parent.SetEnabled(false)
// Re-enable parent on close dialog.OnClose(func() bool { parent.SetEnabled(true) parent.SetFocus() return true })
dialog.Show()
return waitForResult(dialog)}type Formdialog struct { window *application.WebviewWindow data map[string]interface{} done chan bool}
func NewFormdialog(app *application.Application) *Formdialog { fd := &Formdialog{ data: make(map[string]interface{}), done: make(chan bool, 1), }
fd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Enter Information", Width: 500, Height: 400, Frameless: true, Hidden: true, })
return fd}
func (fd *Formdialog) Show() (map[string]interface{}, bool) { fd.window.Show() fd.window.SetFocus()
ok := <-fd.done return fd.data, ok}
func (fd *Formdialog) Submit(data map[string]interface{}) { fd.data = data fd.done <- true fd.window.Close()}
func (fd *Formdialog) Cancel() { fd.done <- false fd.window.Close()}func ShowConfirmdialog(message string) bool { dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Confirm", Width: 400, Height: 150, AlwaysOnTop: true, Frameless: true, })
// Pass message to dialog dialog.OnReady(func() { dialog.EmitEvent("set-message", message) })
result := make(chan bool, 1)
// Handle responses app.Event.On("confirm-yes", func(e *application.CustomEvent) { result <- true dialog.Close() })
app.Event.On("confirm-no", func(e *application.CustomEvent) { result <- false dialog.Close() })
dialog.Show() return <-result}Frontend (HTML/JS):
<div class="dialog"> <h2 id="message"></h2> <div class="buttons"> <button onclick="confirm(true)">Yes</button> <button onclick="confirm(false)">No</button> </div></div>
<script>import { Events } from '@wailsio/runtime'
Events.On("set-message", (message) => { document.getElementById("message").textContent = message})
function confirm(result) { Events.Emit(result ? "confirm-yes" : "confirm-no")}</script>func ShowInputdialog(prompt string, defaultValue string) (string, bool) { dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Input", Width: 400, Height: 150, Frameless: true, })
result := make(chan struct { value string ok bool }, 1)
dialog.OnReady(func() { dialog.EmitEvent("set-prompt", map[string]string{ "prompt": prompt, "default": defaultValue, }) })
app.Event.On("input-submit", func(e *application.CustomEvent) { result <- struct { value string ok bool }{e.Data.(string), true} dialog.Close() })
app.Event.On("input-cancel", func(e *application.CustomEvent) { result <- struct { value string ok bool }{"", false} dialog.Close() })
dialog.Show() r := <-result return r.value, r.ok}type Progressdialog struct { window *application.WebviewWindow}
func NewProgressdialog(title string) *Progressdialog { pd := &Progressdialog{}
pd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 150, Frameless: true, })
return pd}
func (pd *Progressdialog) Show() { pd.window.Show()}
func (pd *Progressdialog) UpdateProgress(current, total int, message string) { pd.window.EmitEvent("progress-update", map[string]interface{}{ "current": current, "total": total, "message": message, })}
func (pd *Progressdialog) Close() { pd.window.Close()}Usage:
func processFiles(files []string) { progress := NewProgressdialog("Processing Files") progress.Show()
for i, file := range files { progress.UpdateProgress(i+1, len(files), fmt.Sprintf("Processing %s...", filepath.Base(file)))
processFile(file) }
progress.Close()}Go:
type Logindialog struct { window *application.WebviewWindow result chan struct { username string password string ok bool }}
func NewLogindialog(app *application.Application) *Logindialog { ld := &Logindialog{ result: make(chan struct { username string password string ok bool }, 1), }
ld.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Login", Width: 400, Height: 250, Frameless: true, })
return ld}
func (ld *Logindialog) Show() (string, string, bool) { ld.window.Show() ld.window.SetFocus()
r := <-ld.result return r.username, r.password, r.ok}
func (ld *Logindialog) Submit(username, password string) { ld.result <- struct { username string password string ok bool }{username, password, true} ld.window.Close()}
func (ld *Logindialog) Cancel() { ld.result <- struct { username string password string ok bool }{"", "", false} ld.window.Close()}Frontend:
<div class="login-dialog"> <h2>Login</h2> <form id="login-form"> <input type="text" id="username" placeholder="Username" required> <input type="password" id="password" placeholder="Password" required> <div class="buttons"> <button type="submit">Login</button> <button type="button" onclick="cancel()">Cancel</button> </div> </form></div>
<script>import { Events } from '@wailsio/runtime'
document.getElementById('login-form').addEventListener('submit', (e) => { e.preventDefault() const username = document.getElementById('username').value const password = document.getElementById('password').value Events.Emit('login-submit', { username, password })})
function cancel() { Events.Emit('login-cancel')}</script>Go:
type Settingsdialog struct { window *application.WebviewWindow settings map[string]interface{} done chan bool}
func NewSettingsdialog(app *application.Application, current map[string]interface{}) *Settingsdialog { sd := &Settingsdialog{ settings: current, done: make(chan bool, 1), }
sd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Settings", Width: 600, Height: 500, })
sd.window.OnReady(func() { sd.window.EmitEvent("load-settings", current) })
return sd}
func (sd *Settingsdialog) Show() (map[string]interface{}, bool) { sd.window.Show()
ok := <-sd.done return sd.settings, ok}
func (sd *Settingsdialog) Save(settings map[string]interface{}) { sd.settings = settings sd.done <- true sd.window.Close()}
func (sd *Settingsdialog) Cancel() { sd.done <- false sd.window.Close()}type Wizarddialog struct { window *application.WebviewWindow currentStep int data map[string]interface{} done chan bool}
func NewWizarddialog(app *application.Application) *Wizarddialog { wd := &Wizarddialog{ currentStep: 0, data: make(map[string]interface{}), done: make(chan bool, 1), }
wd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Setup Wizard", Width: 600, Height: 400, Resizable: false, })
return wd}
func (wd *Wizarddialog) Show() (map[string]interface{}, bool) { wd.window.Show()
ok := <-wd.done return wd.data, ok}
func (wd *Wizarddialog) NextStep(stepData map[string]interface{}) { // Merge step data for k, v := range stepData { wd.data[k] = v }
wd.currentStep++ wd.window.EmitEvent("next-step", wd.currentStep)}
func (wd *Wizarddialog) PreviousStep() { if wd.currentStep > 0 { wd.currentStep-- wd.window.EmitEvent("previous-step", wd.currentStep) }}
func (wd *Wizarddialog) Finish(finalData map[string]interface{}) { for k, v := range finalData { wd.data[k] = v }
wd.done <- true wd.window.Close()}
func (wd *Wizarddialog) Cancel() { wd.done <- false wd.window.Close()}.dialog { display: flex; flex-direction: column; height: 100vh; background: white; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;}
.dialog-header { --wails-draggable: drag; padding: 16px; background: #f5f5f5; border-bottom: 1px solid #e0e0e0;}
.dialog-content { flex: 1; padding: 24px; overflow: auto;}
.dialog-footer { padding: 16px; background: #f5f5f5; border-top: 1px solid #e0e0e0; display: flex; justify-content: flex-end; gap: 8px;}
button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;}
button.primary { background: #007aff; color: white;}
button.secondary { background: #e0e0e0; color: #333;}Message dialogs
Standard info, warning, error dialogs.
File dialogs
Open, save, folder selection.
Windows
Learn about window management.
Events
Use events for dialog communication.
Questions? Ask in Discord or check the custom dialog examples.