--- title: Custom dialogs sidebar: order: 4 --- import { Card, CardGrid } from "@astrojs/starlight/components"; ## Custom 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. ## Quick Start ```go // Create custom dialog window dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Custom dialog", Width: 400, Height: 300, AlwaysOnTop: true, Frameless: true, Hidden: true, }) // Load custom UI dialog.SetURL("http://wails.localhost/dialog.html") // Show as modal dialog.Show() dialog.SetFocus() ``` **That's it!** Custom UI with dialog behaviour. ## Creating Custom dialogs ### Basic Custom dialog ```go 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() } ``` ### Modal dialog ```go 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) } ``` ### Form dialog ```go 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() } ``` ## dialog Patterns ### Confirmation dialog ```go 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):** ```html

``` ### Input dialog ```go 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 } ``` ### Progress dialog ```go 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:** ```go 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() } ``` ## Complete Examples ### Login dialog **Go:** ```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:** ```html

Login

``` ### Settings dialog **Go:** ```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() } ``` ### Wizard dialog ```go 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() } ``` ## Best Practices ### ✅ Do - **Use appropriate window options** - AlwaysOnTop, Frameless, etc. - **Handle cancellation** - Always provide a way to cancel - **Validate input** - Check data before accepting - **Provide feedback** - Loading states, errors - **Use events for communication** - Clean separation - **Clean up resources** - Close windows, remove listeners ### ❌ Don't - **Don't block the main thread** - Use channels for results - **Don't forget to close** - Memory leaks - **Don't skip validation** - Always validate input - **Don't ignore errors** - Handle all error cases - **Don't make it too complex** - Keep dialogs simple - **Don't forget accessibility** - Keyboard navigation ## Styling Custom dialogs ### Modern dialog Style ```css .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; } ``` ## Next Steps Standard info, warning, error dialogs. [Learn More →](/features/dialogs/message) Open, save, folder selection. [Learn More →](/features/dialogs/file) Learn about window management. [Learn More →](/features/windows/basics) Use events for dialog communication. [Learn More →](/features/events/system) --- **Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [custom dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).