Skip to content

Window Events

Wails provides comprehensive event hooks for window lifecycle and state changes: creation, focus/blur, resize/move, minimise/maximise, and close events. Register callbacks, handle events, and coordinate between windows with simple, consistent APIs.

Called when a window is created:

app.OnWindowCreation(func(window *application.WebviewWindow) {
fmt.Printf("Window created: %s (ID: %d)\n", window.Name(), window.ID())
// Configure all new windows
window.SetMinSize(400, 300)
// Register window-specific handlers
window.OnClose(func() bool {
return confirmClose()
})
})

Use cases:

  • Configure all windows consistently
  • Register event handlers
  • Track window creation
  • Initialise window-specific resources

Called when user attempts to close window:

window.OnClose(func() bool {
// Return false to cancel close
// Return true to allow close
if hasUnsavedChanges() {
result := showConfirmdialog("Unsaved changes. Close anyway?")
return result == "yes"
}
return true
})

Important:

  • Only triggered by user actions (clicking X button)
  • NOT triggered by window.Destroy()
  • Can cancel the close by returning false

Use cases:

  • Confirm before closing
  • Save state
  • Prevent accidental closure
  • Cleanup before close

Example with dialog:

window.OnClose(func() bool {
if !hasUnsavedChanges() {
return true
}
// Show confirmation dialog
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Confirm Close",
Width: 400,
Height: 150,
Parent: window,
AlwaysOnTop: true,
})
// Wait for user response
result := waitFordialogResult(dialog)
return result == "yes"
})

Called when window is destroyed:

window.OnDestroy(func() {
fmt.Printf("Window destroyed: %s\n", window.Name())
// Cleanup resources
closeDatabase()
// Remove from tracking
removeWindowFromRegistry(window.ID())
// Update application state
updateWindowCount()
})

Important:

  • Always called when window is destroyed
  • Cannot be cancelled
  • Last chance to cleanup

Use cases:

  • Release resources
  • Close connections
  • Update application state
  • Remove from tracking

Example with resource cleanup:

type ManagedWindow struct {
window *application.WebviewWindow
db *sql.DB
listeners []func()
}
func (mw *ManagedWindow) Setup() {
mw.window.OnDestroy(func() {
// Close database
if mw.db != nil {
mw.db.Close()
}
// Remove event listeners
for _, listener := range mw.listeners {
listener()
}
// Clear references
mw.db = nil
mw.listeners = nil
})
}

Called when window gains focus:

window.OnFocus(func() {
fmt.Println("Window gained focus")
// Update UI
updateTitleBar(true)
// Refresh data
refreshContent()
// Notify other windows
app.Event.Emit("window-focused", window.ID())
})

Use cases:

  • Update UI appearance
  • Refresh data
  • Resume operations
  • Coordinate with other windows

Called when window loses focus:

window.OnBlur(func() {
fmt.Println("Window lost focus")
// Update UI
updateTitleBar(false)
// Pause operations
pauseAnimations()
// Save state
saveCurrentState()
})

Use cases:

  • Update UI appearance
  • Pause operations
  • Save state
  • Reduce resource usage

Example: Focus-aware UI:

type FocusAwareWindow struct {
window *application.WebviewWindow
focused bool
}
func (fw *FocusAwareWindow) Setup() {
fw.window.OnFocus(func() {
fw.focused = true
fw.updateAppearance()
})
fw.window.OnBlur(func() {
fw.focused = false
fw.updateAppearance()
})
}
func (fw *FocusAwareWindow) updateAppearance() {
if fw.focused {
fw.window.EmitEvent("update-theme", "active")
} else {
fw.window.EmitEvent("update-theme", "inactive")
}
}

Called when window is minimised or restored:

window.OnMinimise(func() {
fmt.Println("Window minimised")
// Pause expensive operations
pauseRendering()
// Save state
saveWindowState()
})
window.OnUnMinimise(func() {
fmt.Println("Window restored from minimised")
// Resume operations
resumeRendering()
// Refresh data
refreshContent()
})

Use cases:

  • Pause/resume operations
  • Save/restore state
  • Reduce resource usage
  • Update UI

Called when window is maximised or restored:

window.OnMaximise(func() {
fmt.Println("Window maximised")
// Adjust layout
window.EmitEvent("layout-mode", "maximised")
// Update button icon
updateMaximiseButton("restore")
})
window.OnUnMaximise(func() {
fmt.Println("Window restored from maximised")
// Adjust layout
window.EmitEvent("layout-mode", "normal")
// Update button icon
updateMaximiseButton("maximise")
})

Use cases:

  • Adjust layout
  • Update UI
  • Save window state
  • Coordinate with other windows

Called when window enters or exits fullscreen:

window.OnFullscreen(func() {
fmt.Println("Window entered fullscreen")
// Hide UI chrome
window.EmitEvent("chrome-visibility", false)
// Adjust layout
window.EmitEvent("layout-mode", "fullscreen")
})
window.OnUnFullscreen(func() {
fmt.Println("Window exited fullscreen")
// Show UI chrome
window.EmitEvent("chrome-visibility", true)
// Restore layout
window.EmitEvent("layout-mode", "normal")
})

Use cases:

  • Show/hide UI elements
  • Adjust layout
  • Update controls
  • Save preferences

Called when window is moved:

window.OnMove(func(x, y int) {
fmt.Printf("Window moved to: %d, %d\n", x, y)
// Save position
saveWindowPosition(x, y)
// Update related windows
updateRelatedWindowPositions(x, y)
})

Use cases:

  • Save window position
  • Update related windows
  • Snap to edges
  • Multi-monitor handling

Called when window is resized:

window.OnResize(func(width, height int) {
fmt.Printf("Window resized to: %dx%d\n", width, height)
// Save size
saveWindowSize(width, height)
// Adjust layout
window.EmitEvent("window-size", map[string]int{
"width": width,
"height": height,
})
})

Use cases:

  • Save window size
  • Adjust layout
  • Update UI
  • Responsive design

Example: Responsive layout:

window.OnResize(func(width, height int) {
var layout string
if width < 600 {
layout = "compact"
} else if width < 1200 {
layout = "normal"
} else {
layout = "wide"
}
window.EmitEvent("layout-changed", layout)
})

Here’s a production-ready window with full event handling:

package main
import (
"encoding/json"
"fmt"
"os"
"github.com/wailsapp/wails/v3/pkg/application"
)
type WindowState struct {
X int `json:"x"`
Y int `json:"y"`
Width int `json:"width"`
Height int `json:"height"`
Maximised bool `json:"maximised"`
Fullscreen bool `json:"fullscreen"`
}
type ManagedWindow struct {
app *application.Application
window *application.WebviewWindow
state WindowState
dirty bool
}
func main() {
app := application.New(application.Options{
Name: "Event Demo",
})
mw := &ManagedWindow{app: app}
mw.CreateWindow()
mw.LoadState()
mw.SetupEventHandlers()
app.Run()
}
func (mw *ManagedWindow) CreateWindow() {
mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main",
Title: "Event Demo",
Width: 800,
Height: 600,
})
}
func (mw *ManagedWindow) SetupEventHandlers() {
// Focus events
mw.window.OnFocus(func() {
fmt.Println("Window focused")
mw.window.EmitEvent("focus-state", true)
})
mw.window.OnBlur(func() {
fmt.Println("Window blurred")
mw.window.EmitEvent("focus-state", false)
})
// State change events
mw.window.OnMinimise(func() {
fmt.Println("Window minimised")
mw.SaveState()
})
mw.window.OnUnMinimise(func() {
fmt.Println("Window restored")
})
mw.window.OnMaximise(func() {
fmt.Println("Window maximised")
mw.state.Maximised = true
mw.dirty = true
})
mw.window.OnUnMaximise(func() {
fmt.Println("Window restored from maximised")
mw.state.Maximised = false
mw.dirty = true
})
mw.window.OnFullscreen(func() {
fmt.Println("Window fullscreen")
mw.state.Fullscreen = true
mw.dirty = true
})
mw.window.OnUnFullscreen(func() {
fmt.Println("Window exited fullscreen")
mw.state.Fullscreen = false
mw.dirty = true
})
// Position and size events
mw.window.OnMove(func(x, y int) {
mw.state.X = x
mw.state.Y = y
mw.dirty = true
})
mw.window.OnResize(func(width, height int) {
mw.state.Width = width
mw.state.Height = height
mw.dirty = true
})
// Lifecycle events
mw.window.OnClose(func() bool {
if mw.dirty {
mw.SaveState()
}
return true
})
mw.window.OnDestroy(func() {
fmt.Println("Window destroyed")
if mw.dirty {
mw.SaveState()
}
})
}
func (mw *ManagedWindow) LoadState() {
data, err := os.ReadFile("window-state.json")
if err != nil {
return
}
if err := json.Unmarshal(data, &mw.state); err != nil {
return
}
// Restore window state
mw.window.SetPosition(mw.state.X, mw.state.Y)
mw.window.SetSize(mw.state.Width, mw.state.Height)
if mw.state.Maximised {
mw.window.Maximise()
}
if mw.state.Fullscreen {
mw.window.Fullscreen()
}
}
func (mw *ManagedWindow) SaveState() {
data, err := json.Marshal(mw.state)
if err != nil {
return
}
os.WriteFile("window-state.json", data, 0644)
mw.dirty = false
fmt.Println("Window state saved")
}

Coordinate between multiple windows:

// In main window
mainWindow.OnFocus(func() {
// Notify all windows
app.Event.Emit("main-window-focused", nil)
})
// In other windows
app.Event.On("main-window-focused", func(event *application.WailsEvent) {
// Update UI
updateRelativeToMain()
})

Chain events together:

window.OnMaximise(func() {
// Save state
saveWindowState()
// Update layout
window.EmitEvent("layout-changed", "maximised")
// Notify other windows
app.Event.Emit("window-maximised", window.ID())
})

Debounce frequent events:

var resizeTimer *time.Timer
window.OnResize(func(width, height int) {
if resizeTimer != nil {
resizeTimer.Stop()
}
resizeTimer = time.AfterFunc(500*time.Millisecond, func() {
// Save after resize stops
saveWindowSize(width, height)
})
})
  • Save state on close - Restore window position/size
  • Cleanup on destroy - Release resources
  • Debounce frequent events - Resize, move
  • Handle focus changes - Update UI appropriately
  • Coordinate windows - Use events for communication
  • Test all events - Ensure handlers work correctly
  • Don’t block event handlers - Keep them fast
  • Don’t forget cleanup - Memory leaks
  • Don’t ignore errors - Log or handle them
  • Don’t save on every event - Debounce first
  • Don’t create circular events - Infinite loops
  • Don’t forget platform differences - Test thoroughly

Cause: Using window.Destroy() instead of window.Close()

Solution:

// ✅ Triggers OnClose
window.Close()
// ❌ Doesn't trigger OnClose
window.Destroy()

Cause: Handler registered after event occurred

Solution:

// Register handlers immediately after creation
window := app.Window.New()
window.OnClose(func() bool { return true })

Cause: Not cleaning up in OnDestroy

Solution:

window.OnDestroy(func() {
// Always cleanup
closeResources()
removeReferences()
})

Window Basics - Learn the fundamentals of window management
Learn More →

Multiple Windows - Patterns for multi-window applications
Learn More →

Events System - Deep dive into the event system
Learn More →

Application Lifecycle - Understand the application lifecycle
Learn More →


Questions? Ask in Discord or check the examples.