+```
+
+### Context Data Not Received
+
+**Possible causes:**
+1. CSS property not set
+2. Data contains special characters
+
+**Solution:**
+
+```html
+
+
+```
+
+Or use JavaScript:
+
+```javascript
+element.style.setProperty('--custom-contextmenu-data', JSON.stringify(data))
+```
+
+### Menu Items Not Responding
+
+**Cause:** Forgot to call `menu.Update()` after enabling
+
+**Solution:**
+
+```go
+menuItem.SetEnabled(true)
+contextMenu.Update() // Add this!
+```
+
+## Next Steps
+
+
+
+ Complete reference for menu item types and properties.
+
+ [Learn More →](/features/menus/reference)
+
+
+
+ Create application menu bars.
+
+ [Learn More →](/features/menus/application)
+
+
+
+ Add system tray/menu bar integration.
+
+ [Learn More →](/features/menus/systray)
+
+
+
+ Common menu patterns and best practices.
+
+ [Learn More →](/guides/patterns/menus)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [context menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/contextmenus).
diff --git a/docs/src/content/docs/features/menus/reference.mdx b/docs/src/content/docs/features/menus/reference.mdx
new file mode 100644
index 000000000..93cc6b45a
--- /dev/null
+++ b/docs/src/content/docs/features/menus/reference.mdx
@@ -0,0 +1,562 @@
+---
+title: Menu Reference
+description: Complete reference for menu item types, properties, and methods
+sidebar:
+ order: 4
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+
+## Menu Reference
+
+Complete reference for menu item types, properties, and dynamic behaviour. Build professional, responsive menus with checkboxes, radio groups, separators, and dynamic updates.
+
+## Menu Item Types
+
+### Regular Menu Items
+
+The most common type—displays text and triggers an action:
+
+```go
+menuItem := menu.Add("Click Me")
+menuItem.OnClick(func(ctx *application.Context) {
+ fmt.Println("Menu item clicked!")
+})
+```
+
+**Use for:** Commands, actions, opening windows
+
+### Checkboxes
+
+Toggle-able menu items with checked/unchecked state:
+
+```go
+checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked
+checkbox.OnClick(func(ctx *application.Context) {
+ isChecked := ctx.ClickedMenuItem().Checked()
+ fmt.Printf("Feature is now: %v\n", isChecked)
+})
+```
+
+**Use for:** Boolean settings, feature toggles, view options
+
+**Important:** The checked state toggles automatically when clicked.
+
+### Radio Groups
+
+Mutually exclusive options—only one can be selected:
+
+```go
+menu.AddRadio("Small", true) // true = initially selected
+menu.AddRadio("Medium", false)
+menu.AddRadio("Large", false)
+```
+
+**Use for:** Mutually exclusive choices (size, theme, mode)
+
+**How grouping works:**
+- Adjacent radio items form a group automatically
+- Selecting one deselects others in the group
+- Separate groups with a separator or regular item
+
+**Example with multiple groups:**
+
+```go
+// Group 1: Size
+menu.AddRadio("Small", true)
+menu.AddRadio("Medium", false)
+menu.AddRadio("Large", false)
+
+menu.AddSeparator()
+
+// Group 2: Theme
+menu.AddRadio("Light", true)
+menu.AddRadio("Dark", false)
+```
+
+### Submenus
+
+Nested menu structures for organisation:
+
+```go
+submenu := menu.AddSubmenu("More Options")
+submenu.Add("Submenu Item 1").OnClick(func(ctx *application.Context) {
+ // Handle click
+})
+submenu.Add("Submenu Item 2")
+```
+
+**Use for:** Grouping related items, reducing clutter
+
+**Nesting limit:** Most platforms support 2-3 levels. Avoid deeper nesting.
+
+### Separators
+
+Visual dividers between menu items:
+
+```go
+menu.Add("Item 1")
+menu.AddSeparator()
+menu.Add("Item 2")
+```
+
+**Use for:** Grouping related items visually
+
+**Best practice:** Don't start or end menus with separators.
+
+## Menu Item Properties
+
+### Label
+
+The text displayed for the menu item:
+
+```go
+menuItem := menu.Add("Initial Label")
+menuItem.SetLabel("New Label")
+
+// Get current label
+label := menuItem.Label()
+```
+
+**Dynamic labels:**
+
+```go
+updateMenuItem := menu.Add("Check for Updates")
+updateMenuItem.OnClick(func(ctx *application.Context) {
+ updateMenuItem.SetLabel("Checking...")
+ menu.Update() // Important on Windows!
+
+ // Perform update check
+ checkForUpdates()
+
+ updateMenuItem.SetLabel("Check for Updates")
+ menu.Update()
+})
+```
+
+### Enabled State
+
+Control whether the menu item can be interacted with:
+
+```go
+menuItem := menu.Add("Save")
+menuItem.SetEnabled(false) // Greyed out, can't click
+
+// Enable it later
+menuItem.SetEnabled(true)
+menu.Update() // Important: Call this after changing enabled state!
+
+// Check current state
+isEnabled := menuItem.Enabled()
+```
+
+:::caution[Windows Menu Behaviour]
+On Windows, menus need to be reconstructed when their state changes. **Always call `menu.Update()` after enabling/disabling menu items**, especially if the item was created whilst disabled.
+
+**Why:** Windows menus are rebuilt from scratch when updated. If you don't call `Update()`, click handlers won't fire properly.
+:::
+
+**Example: Dynamic enable/disable**
+
+```go
+var hasSelection bool
+
+cutMenuItem := menu.Add("Cut")
+cutMenuItem.SetEnabled(false) // Initially disabled
+
+copyMenuItem := menu.Add("Copy")
+copyMenuItem.SetEnabled(false)
+
+// When selection changes
+func onSelectionChanged(selected bool) {
+ hasSelection = selected
+ cutMenuItem.SetEnabled(hasSelection)
+ copyMenuItem.SetEnabled(hasSelection)
+ menu.Update() // Critical on Windows!
+}
+```
+
+**Common pattern: Enable on condition**
+
+```go
+saveMenuItem := menu.Add("Save")
+
+func updateSaveMenuItem() {
+ canSave := hasUnsavedChanges() && !isSaving()
+ saveMenuItem.SetEnabled(canSave)
+ menu.Update()
+}
+
+// Call whenever state changes
+onDocumentChanged(func() {
+ updateSaveMenuItem()
+})
+```
+
+### Checked State
+
+For checkbox and radio items, control or query their checked state:
+
+```go
+checkbox := menu.AddCheckbox("Feature", false)
+checkbox.SetChecked(true)
+menu.Update()
+
+// Query state
+isChecked := checkbox.Checked()
+```
+
+**Auto-toggle:** Checkboxes toggle automatically when clicked. You don't need to call `SetChecked()` in the click handler.
+
+**Manual control:**
+
+```go
+checkbox := menu.AddCheckbox("Auto-save", false)
+
+// Sync with external state
+func syncAutoSave(enabled bool) {
+ checkbox.SetChecked(enabled)
+ menu.Update()
+}
+```
+
+### Accelerators (Keyboard Shortcuts)
+
+Add keyboard shortcuts to menu items:
+
+```go
+saveMenuItem := menu.Add("Save")
+saveMenuItem.SetAccelerator("CmdOrCtrl+S")
+
+quitMenuItem := menu.Add("Quit")
+quitMenuItem.SetAccelerator("CmdOrCtrl+Q")
+```
+
+**Accelerator format:**
+- `CmdOrCtrl` - Cmd on macOS, Ctrl on Windows/Linux
+- `Shift`, `Alt`, `Option` - Modifier keys
+- `A-Z`, `0-9` - Letter/number keys
+- `F1-F12` - Function keys
+- `Enter`, `Space`, `Backspace`, etc. - Special keys
+
+**Examples:**
+
+```go
+"CmdOrCtrl+S" // Save
+"CmdOrCtrl+Shift+S" // Save As
+"CmdOrCtrl+W" // Close Window
+"CmdOrCtrl+Q" // Quit
+"F5" // Refresh
+"CmdOrCtrl+," // Preferences (macOS convention)
+"Alt+F4" // Close (Windows convention)
+```
+
+**Platform-specific accelerators:**
+
+```go
+if runtime.GOOS == "darwin" {
+ prefsMenuItem.SetAccelerator("Cmd+,")
+} else {
+ prefsMenuItem.SetAccelerator("Ctrl+P")
+}
+```
+
+### Tooltip
+
+Add hover text to menu items (platform support varies):
+
+```go
+menuItem := menu.Add("Advanced Options")
+menuItem.SetTooltip("Configure advanced settings")
+```
+
+**Platform support:**
+- **Windows:** ✅ Supported
+- **macOS:** ❌ Not supported (tooltips not standard for menus)
+- **Linux:** ⚠️ Varies by desktop environment
+
+### Hidden State
+
+Hide menu items without removing them:
+
+```go
+debugMenuItem := menu.Add("Debug Mode")
+debugMenuItem.SetHidden(true) // Hidden
+
+// Show in debug builds
+if isDebugBuild {
+ debugMenuItem.SetHidden(false)
+ menu.Update()
+}
+```
+
+**Use for:** Debug options, feature flags, conditional features
+
+## Event Handling
+
+### OnClick Handler
+
+Execute code when menu item is clicked:
+
+```go
+menuItem := menu.Add("Click Me")
+menuItem.OnClick(func(ctx *application.Context) {
+ // Handle click
+ fmt.Println("Clicked!")
+})
+```
+
+**Context provides:**
+- `ctx.ClickedMenuItem()` - The menu item that was clicked
+- Window context (if from window menu)
+- Application context
+
+**Example: Access menu item in handler**
+
+```go
+checkbox := menu.AddCheckbox("Feature", false)
+checkbox.OnClick(func(ctx *application.Context) {
+ item := ctx.ClickedMenuItem()
+ isChecked := item.Checked()
+ fmt.Printf("Feature is now: %v\n", isChecked)
+})
+```
+
+### Multiple Handlers
+
+You can set multiple handlers (last one wins):
+
+```go
+menuItem := menu.Add("Action")
+menuItem.OnClick(func(ctx *application.Context) {
+ fmt.Println("First handler")
+})
+
+// This replaces the first handler
+menuItem.OnClick(func(ctx *application.Context) {
+ fmt.Println("Second handler - this one runs")
+})
+```
+
+**Best practice:** Set handler once, use conditional logic inside if needed.
+
+## Dynamic Menus
+
+### Updating Menu Items
+
+**The golden rule:** Always call `menu.Update()` after changing menu state.
+
+```go
+// ✅ Correct
+menuItem.SetEnabled(true)
+menu.Update()
+
+// ❌ Wrong (especially on Windows)
+menuItem.SetEnabled(true)
+// Forgot to call Update() - click handlers may not work!
+```
+
+**Why this matters:**
+- **Windows:** Menus are reconstructed when updated
+- **macOS/Linux:** Less critical but still recommended
+- **Click handlers:** Won't fire properly without Update()
+
+### Rebuilding Menus
+
+For major changes, rebuild the entire menu:
+
+```go
+func rebuildFileMenu() {
+ menu := app.Menu.New()
+
+ menu.Add("New").OnClick(handleNew)
+ menu.Add("Open").OnClick(handleOpen)
+
+ if hasRecentFiles() {
+ recentMenu := menu.AddSubmenu("Open Recent")
+ for _, file := range getRecentFiles() {
+ recentMenu.Add(file).OnClick(func(ctx *application.Context) {
+ openFile(file)
+ })
+ }
+ }
+
+ menu.AddSeparator()
+ menu.Add("Quit").OnClick(handleQuit)
+
+ // Set the new menu
+ window.SetMenu(menu)
+}
+```
+
+**When to rebuild:**
+- Recent files list changes
+- Plugin menus change
+- Major state transitions
+
+**When to update:**
+- Enable/disable items
+- Change labels
+- Toggle checkboxes
+
+### Context-Sensitive Menus
+
+Adjust menus based on application state:
+
+```go
+func updateEditMenu() {
+ cutMenuItem.SetEnabled(hasSelection())
+ copyMenuItem.SetEnabled(hasSelection())
+ pasteMenuItem.SetEnabled(hasClipboardContent())
+ undoMenuItem.SetEnabled(canUndo())
+ redoMenuItem.SetEnabled(canRedo())
+ menu.Update()
+}
+
+// Call whenever state changes
+onSelectionChanged(updateEditMenu)
+onClipboardChanged(updateEditMenu)
+onUndoStackChanged(updateEditMenu)
+```
+
+## Platform Differences
+
+### Menu Bar Location
+
+| Platform | Location | Notes |
+|----------|----------|-------|
+| **macOS** | Top of screen | Global menu bar |
+| **Windows** | Top of window | Per-window menu |
+| **Linux** | Top of window | Per-window (usually) |
+
+### Standard Menus
+
+**macOS:**
+- Has "Application" menu (with app name)
+- "Preferences" in Application menu
+- "Quit" in Application menu
+
+**Windows/Linux:**
+- No Application menu
+- "Preferences" in Edit or Tools menu
+- "Exit" in File menu
+
+**Example: Platform-appropriate structure**
+
+```go
+menu := app.Menu.New()
+
+// macOS gets Application menu
+if runtime.GOOS == "darwin" {
+ menu.AddRole(application.AppMenu)
+}
+
+// File menu
+fileMenu := menu.AddSubmenu("File")
+fileMenu.Add("New")
+fileMenu.Add("Open")
+
+// Preferences location varies
+if runtime.GOOS == "darwin" {
+ // On macOS, preferences are in Application menu (added by AppMenu role)
+} else {
+ // On Windows/Linux, add to Edit or Tools menu
+ editMenu := menu.AddSubmenu("Edit")
+ editMenu.Add("Preferences")
+}
+```
+
+### Accelerator Conventions
+
+**macOS:**
+- `Cmd+` for most shortcuts
+- `Cmd+,` for Preferences
+- `Cmd+Q` for Quit
+
+**Windows:**
+- `Ctrl+` for most shortcuts
+- `Ctrl+P` or `Ctrl+,` for Preferences
+- `Alt+F4` for Exit (or `Ctrl+Q`)
+
+**Linux:**
+- Generally follows Windows conventions
+- Desktop environment may override
+
+## Best Practices
+
+### ✅ Do
+
+- **Call menu.Update()** after changing menu state (especially on Windows)
+- **Use radio groups** for mutually exclusive options
+- **Use checkboxes** for toggleable features
+- **Add accelerators** to common actions
+- **Group related items** with separators
+- **Test on all platforms** - behaviour varies
+
+### ❌ Don't
+
+- **Don't forget menu.Update()** - Click handlers won't work properly
+- **Don't nest too deeply** - 2-3 levels maximum
+- **Don't start/end with separators** - Looks unprofessional
+- **Don't use tooltips on macOS** - Not supported
+- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl`
+
+## Troubleshooting
+
+### Menu Items Not Responding
+
+**Symptom:** Click handlers don't fire
+
+**Cause:** Forgot to call `menu.Update()` after enabling item
+
+**Solution:**
+
+```go
+menuItem.SetEnabled(true)
+menu.Update() // Add this!
+```
+
+### Menu Items Greyed Out
+
+**Symptom:** Can't click menu items
+
+**Cause:** Items are disabled
+
+**Solution:**
+
+```go
+menuItem.SetEnabled(true)
+menu.Update()
+```
+
+### Accelerators Not Working
+
+**Symptom:** Keyboard shortcuts don't trigger menu items
+
+**Causes:**
+1. Accelerator format incorrect
+2. Conflict with system shortcuts
+3. Window doesn't have focus
+
+**Solution:**
+
+```go
+// Check format
+menuItem.SetAccelerator("CmdOrCtrl+S") // ✅ Correct
+menuItem.SetAccelerator("Ctrl+S") // ❌ Wrong (macOS uses Cmd)
+
+// Avoid conflicts
+// ❌ Cmd+H (Hide Window on macOS - system shortcut)
+// ✅ Cmd+Shift+H (Custom shortcut)
+```
+
+## Next Steps
+
+- [Application Menus](/features/menus/application) - Create application menu bars
+- [Context Menus](/features/menus/context) - Right-click context menus
+- [System Tray Menus](/features/menus/systray) - System tray/menu bar menus
+- [Menu Patterns](/guides/patterns/menus) - Common menu patterns and best practices
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).
diff --git a/docs/src/content/docs/features/menus/systray.mdx b/docs/src/content/docs/features/menus/systray.mdx
new file mode 100644
index 000000000..beb81393d
--- /dev/null
+++ b/docs/src/content/docs/features/menus/systray.mdx
@@ -0,0 +1,713 @@
+---
+title: System Tray Menus
+description: Add system tray (notification area) integration to your application
+sidebar:
+ order: 3
+---
+
+import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
+
+## System Tray Menus
+
+Wails provides **unified system tray APIs** that work across all platforms. Create tray icons with menus, attach windows, and handle clicks with native platform behaviour for background applications, services, and quick-access utilities.
+{/* VISUAL PLACEHOLDER: System Tray Comparison
+Description: Three screenshots showing the same Wails system tray icon on:
+1. Windows - Notification area (bottom-right)
+2. macOS - Menu bar (top-right) with label
+3. Linux (GNOME) - Top bar
+All showing the same icon and menu structure
+Style: Clean screenshots with arrows pointing to tray icon, menu expanded
+*/}
+
+## Quick Start
+
+```go
+package main
+
+import (
+ _ "embed"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+//go:embed assets/icon.png
+var icon []byte
+
+func main() {
+ app := application.New(application.Options{
+ Name: "Tray App",
+ })
+
+ // Create system tray
+ systray := app.NewSystemTray()
+ systray.SetIcon(icon)
+ systray.SetLabel("My App")
+
+ // Add menu
+ menu := app.NewMenu()
+ menu.Add("Show").OnClick(func(ctx *application.Context) {
+ // Show main window
+ })
+ menu.Add("Quit").OnClick(func(ctx *application.Context) {
+ app.Quit()
+ })
+ systray.SetMenu(menu)
+
+ // Create hidden window
+ window := app.NewWebviewWindow()
+ window.Hide()
+
+ app.Run()
+}
+```
+
+**Result:** System tray icon with menu on all platforms.
+
+## Creating a System Tray
+
+### Basic System Tray
+
+```go
+// Create system tray
+systray := app.NewSystemTray()
+
+// Set icon
+systray.SetIcon(iconBytes)
+
+// Set label (macOS) / tooltip (Windows)
+systray.SetLabel("My Application")
+```
+
+### With Icon
+
+Icons should be embedded:
+
+```go
+import _ "embed"
+
+//go:embed assets/icon.png
+var icon []byte
+
+//go:embed assets/icon-dark.png
+var iconDark []byte
+
+func main() {
+ app := application.New(application.Options{
+ Name: "My App",
+ })
+
+ systray := app.NewSystemTray()
+ systray.SetIcon(icon)
+ systray.SetDarkModeIcon(iconDark) // macOS dark mode
+
+ app.Run()
+}
+```
+
+**Icon requirements:**
+
+| Platform | Size | Format | Notes |
+|----------|------|--------|-------|
+| **Windows** | 16x16 or 32x32 | PNG, ICO | Notification area |
+| **macOS** | 18x18 to 22x22 | PNG | Menu bar, template recommended |
+| **Linux** | 22x22 to 48x48 | PNG, SVG | Varies by DE |
+
+### Template Icons (macOS)
+
+Template icons adapt to light/dark mode automatically:
+
+```go
+systray.SetTemplateIcon(iconBytes)
+```
+
+**Template icon guidelines:**
+- Use black and clear (transparent) colours only
+- Black becomes white in dark mode
+- Name file with `Template` suffix: `iconTemplate.png`
+- [Design guide](https://bjango.com/articles/designingmenubarextras/)
+
+## Adding Menus
+
+System tray menus work like application menus:
+
+```go
+menu := app.NewMenu()
+
+// Add items
+menu.Add("Open").OnClick(func(ctx *application.Context) {
+ showMainWindow()
+})
+
+menu.AddSeparator()
+
+menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
+ enabled := ctx.ClickedMenuItem().Checked()
+ setStartAtLogin(enabled)
+})
+
+menu.AddSeparator()
+
+menu.Add("Quit").OnClick(func(ctx *application.Context) {
+ app.Quit()
+})
+
+// Set menu
+systray.SetMenu(menu)
+```
+
+**For all menu item types**, see [Menu Reference](/features/menus/reference).
+
+## Attaching Windows
+
+Attach a window to the tray icon for automatic show/hide:
+
+```go
+// Create window
+window := app.NewWebviewWindow()
+
+// Attach to tray
+systray.AttachWindow(window)
+
+// Configure behaviour
+systray.SetWindowOffset(10) // Pixels from tray icon
+systray.SetWindowDebounce(200 * time.Millisecond) // Click debounce
+```
+
+**Behaviour:**
+- Window starts hidden
+- **Left-click tray icon** → Toggle window visibility
+- **Right-click tray icon** → Show menu (if set)
+- Window positioned near tray icon
+
+**Example: Popup window**
+
+```go
+window := app.NewWebviewWindow(application.WebviewWindowOptions{
+ Title: "Quick Access",
+ Width: 300,
+ Height: 400,
+ Frameless: true, // No title bar
+ AlwaysOnTop: true, // Stay on top
+})
+
+systray.AttachWindow(window)
+systray.SetWindowOffset(5)
+```
+
+## Click Handlers
+
+Handle tray icon clicks:
+
+```go
+systray := app.NewSystemTray()
+
+// Left click
+systray.OnClick(func() {
+ fmt.Println("Tray icon clicked")
+})
+
+// Right click
+systray.OnRightClick(func() {
+ fmt.Println("Tray icon right-clicked")
+})
+
+// Double click
+systray.OnDoubleClick(func() {
+ fmt.Println("Tray icon double-clicked")
+})
+
+// Mouse enter/leave
+systray.OnMouseEnter(func() {
+ fmt.Println("Mouse entered tray icon")
+})
+
+systray.OnMouseLeave(func() {
+ fmt.Println("Mouse left tray icon")
+})
+```
+
+**Platform support:**
+
+| Event | Windows | macOS | Linux |
+|-------|---------|-------|-------|
+| OnClick | ✅ | ✅ | ✅ |
+| OnRightClick | ✅ | ✅ | ✅ |
+| OnDoubleClick | ✅ | ✅ | ⚠️ Varies |
+| OnMouseEnter | ✅ | ✅ | ⚠️ Varies |
+| OnMouseLeave | ✅ | ✅ | ⚠️ Varies |
+
+## Dynamic Updates
+
+Update tray icon and menu dynamically:
+
+### Change Icon
+
+```go
+var isActive bool
+
+func updateTrayIcon() {
+ if isActive {
+ systray.SetIcon(activeIcon)
+ systray.SetLabel("Active")
+ } else {
+ systray.SetIcon(inactiveIcon)
+ systray.SetLabel("Inactive")
+ }
+}
+```
+
+### Update Menu
+
+```go
+var isPaused bool
+
+pauseMenuItem := menu.Add("Pause")
+
+pauseMenuItem.OnClick(func(ctx *application.Context) {
+ isPaused = !isPaused
+
+ if isPaused {
+ pauseMenuItem.SetLabel("Resume")
+ } else {
+ pauseMenuItem.SetLabel("Pause")
+ }
+
+ menu.Update() // Important!
+})
+```
+
+:::caution[Always Call Update()]
+After changing menu state, **call `menu.Update()`**. See [Menu Reference](/features/menus/reference#enabled-state).
+:::
+
+### Rebuild Menu
+
+For major changes, rebuild the entire menu:
+
+```go
+func rebuildTrayMenu(status string) {
+ menu := app.NewMenu()
+
+ // Status-specific items
+ switch status {
+ case "syncing":
+ menu.Add("Syncing...").SetEnabled(false)
+ menu.Add("Pause Sync").OnClick(pauseSync)
+ case "synced":
+ menu.Add("Up to date ✓").SetEnabled(false)
+ menu.Add("Sync Now").OnClick(startSync)
+ case "error":
+ menu.Add("Sync Error").SetEnabled(false)
+ menu.Add("Retry").OnClick(retrySync)
+ }
+
+ menu.AddSeparator()
+ menu.Add("Quit").OnClick(func(ctx *application.Context) {
+ app.Quit()
+ })
+
+ systray.SetMenu(menu)
+}
+```
+
+## Platform-Specific Features
+
+
+
+ **Menu bar integration:**
+
+ ```go
+ // Set label (appears next to icon)
+ systray.SetLabel("My App")
+
+ // Use template icon (adapts to dark mode)
+ systray.SetTemplateIcon(iconBytes)
+
+ // Set icon position
+ systray.SetIconPosition(application.IconPositionRight)
+ ```
+
+ **Icon positions:**
+ - `IconPositionLeft` - Icon left of label
+ - `IconPositionRight` - Icon right of label
+ - `IconPositionOnly` - Icon only, no label
+ - `IconPositionNone` - Label only, no icon
+
+ **Best practices:**
+ - Use template icons (black + transparent)
+ - Keep labels short (3-5 characters)
+ - 18x18 to 22x22 pixels for Retina displays
+ - Test in both light and dark modes
+
+
+
+ **Notification area integration:**
+
+ ```go
+ // Set tooltip (appears on hover)
+ systray.SetTooltip("My Application")
+
+ // Or use SetLabel (same as tooltip on Windows)
+ systray.SetLabel("My Application")
+
+ // Show/Hide functionality (fully functional)
+ systray.Show() // Show tray icon
+ systray.Hide() // Hide tray icon
+ ```
+
+ **Icon requirements:**
+ - 16x16 or 32x32 pixels
+ - PNG or ICO format
+ - Transparent background
+
+ **Tooltip limits:**
+ - Maximum 127 UTF-16 characters
+ - Longer tooltips will be truncated
+ - Keep concise for best experience
+
+ **Platform features:**
+ - Tray icon survives Windows Explorer restarts
+ - Show() and Hide() methods fully functional
+ - Proper lifecycle management
+
+ **Best practices:**
+ - Use 32x32 for high-DPI displays
+ - Keep tooltips under 127 characters
+ - Test on different Windows versions
+ - Consider notification area overflow
+ - Use Show/Hide for conditional tray visibility
+
+
+
+ **System tray integration:**
+
+ Uses StatusNotifierItem specification (most modern DEs).
+
+ ```go
+ systray.SetIcon(iconBytes)
+ systray.SetLabel("My App")
+ ```
+
+ **Desktop environment support:**
+ - **GNOME**: Top bar (with extension)
+ - **KDE Plasma**: System tray
+ - **XFCE**: Notification area
+ - **Others**: Varies
+
+ **Best practices:**
+ - Use 22x22 or 24x24 pixels
+ - SVG icons scale better
+ - Test on target desktop environments
+ - Provide fallback for unsupported DEs
+
+
+
+## Complete Example
+
+Here's a production-ready system tray application:
+
+```go
+package main
+
+import (
+ _ "embed"
+ "fmt"
+ "time"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+//go:embed assets/icon.png
+var icon []byte
+
+//go:embed assets/icon-active.png
+var iconActive []byte
+
+type TrayApp struct {
+ app *application.Application
+ systray *application.SystemTray
+ window *application.WebviewWindow
+ menu *application.Menu
+ isActive bool
+}
+
+func main() {
+ app := application.New(application.Options{
+ Name: "Tray Application",
+ Mac: application.MacOptions{
+ ApplicationShouldTerminateAfterLastWindowClosed: false,
+ },
+ })
+
+ trayApp := &TrayApp{app: app}
+ trayApp.setup()
+
+ app.Run()
+}
+
+func (t *TrayApp) setup() {
+ // Create system tray
+ t.systray = t.app.NewSystemTray()
+ t.systray.SetIcon(icon)
+ t.systray.SetLabel("Inactive")
+
+ // Create menu
+ t.createMenu()
+
+ // Create window (hidden by default)
+ t.window = t.app.NewWebviewWindow(application.WebviewWindowOptions{
+ Title: "Tray Application",
+ Width: 400,
+ Height: 600,
+ Hidden: true,
+ })
+
+ // Attach window to tray
+ t.systray.AttachWindow(t.window)
+ t.systray.SetWindowOffset(10)
+
+ // Handle tray clicks
+ t.systray.OnRightClick(func() {
+ t.systray.OpenMenu()
+ })
+
+ // Start background task
+ go t.backgroundTask()
+}
+
+func (t *TrayApp) createMenu() {
+ t.menu = t.app.NewMenu()
+
+ // Status item (disabled)
+ statusItem := t.menu.Add("Status: Inactive")
+ statusItem.SetEnabled(false)
+
+ t.menu.AddSeparator()
+
+ // Toggle active
+ t.menu.Add("Start").OnClick(func(ctx *application.Context) {
+ t.toggleActive()
+ })
+
+ // Show window
+ t.menu.Add("Show Window").OnClick(func(ctx *application.Context) {
+ t.window.Show()
+ t.window.SetFocus()
+ })
+
+ t.menu.AddSeparator()
+
+ // Settings
+ t.menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
+ enabled := ctx.ClickedMenuItem().Checked()
+ t.setStartAtLogin(enabled)
+ })
+
+ t.menu.AddSeparator()
+
+ // Quit
+ t.menu.Add("Quit").OnClick(func(ctx *application.Context) {
+ t.app.Quit()
+ })
+
+ t.systray.SetMenu(t.menu)
+}
+
+func (t *TrayApp) toggleActive() {
+ t.isActive = !t.isActive
+ t.updateTray()
+}
+
+func (t *TrayApp) updateTray() {
+ if t.isActive {
+ t.systray.SetIcon(iconActive)
+ t.systray.SetLabel("Active")
+ } else {
+ t.systray.SetIcon(icon)
+ t.systray.SetLabel("Inactive")
+ }
+
+ // Rebuild menu with new status
+ t.createMenu()
+}
+
+func (t *TrayApp) backgroundTask() {
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+
+ for range ticker.C {
+ if t.isActive {
+ fmt.Println("Background task running...")
+ // Do work
+ }
+ }
+}
+
+func (t *TrayApp) setStartAtLogin(enabled bool) {
+ // Implementation varies by platform
+ fmt.Printf("Start at login: %v\n", enabled)
+}
+```
+
+## Visibility Control
+
+Show/hide the tray icon dynamically:
+
+```go
+// Hide tray icon
+systray.Hide()
+
+// Show tray icon
+systray.Show()
+
+// Check visibility
+if systray.IsVisible() {
+ fmt.Println("Tray icon is visible")
+}
+```
+
+**Platform Support:**
+
+| Platform | Hide() | Show() | Notes |
+|----------|--------|--------|-------|
+| **Windows** | ✅ | ✅ | Fully functional - icon appears/disappears from notification area |
+| **macOS** | ✅ | ✅ | Menu bar item shows/hides |
+| **Linux** | ✅ | ✅ | Varies by desktop environment |
+
+**Use cases:**
+- Temporarily hide tray icon based on user preference
+- Headless mode with tray icon appearing only when needed
+- Toggle visibility based on application state
+
+**Example - Conditional Tray Visibility:**
+
+```go
+func (t *TrayApp) setTrayVisibility(visible bool) {
+ if visible {
+ t.systray.Show()
+ } else {
+ t.systray.Hide()
+ }
+}
+
+// Show tray only when updates are available
+func (t *TrayApp) checkForUpdates() {
+ if hasUpdates {
+ t.systray.Show()
+ t.systray.SetLabel("Update Available")
+ } else {
+ t.systray.Hide()
+ }
+}
+```
+
+## Cleanup
+
+Destroy the tray icon when done:
+
+```go
+// In OnShutdown
+app := application.New(application.Options{
+ OnShutdown: func() {
+ if systray != nil {
+ systray.Destroy()
+ }
+ },
+})
+```
+
+**Important:** Always destroy system tray on shutdown to release resources.
+
+## Best Practices
+
+### ✅ Do
+
+- **Use template icons on macOS** - Adapts to dark mode
+- **Keep labels short** - 3-5 characters maximum
+- **Provide tooltips on Windows** - Helps users identify your app
+- **Test on all platforms** - Behaviour varies
+- **Handle clicks appropriately** - Left-click for main action, right-click for menu
+- **Update icon for status** - Visual feedback is important
+- **Destroy on shutdown** - Release resources
+
+### ❌ Don't
+
+- **Don't use large icons** - Follow platform guidelines
+- **Don't use long labels** - Gets truncated
+- **Don't forget dark mode** - Test on macOS dark mode
+- **Don't block click handlers** - Keep them fast
+- **Don't forget menu.Update()** - After changing menu state
+- **Don't assume tray support** - Some Linux DEs don't support it
+
+## Troubleshooting
+
+### Tray Icon Not Appearing
+
+**Possible causes:**
+1. Icon format not supported
+2. Icon size too large/small
+3. System tray not supported (Linux)
+
+**Solution:**
+
+```go
+// Check if system tray is supported
+if !application.SystemTraySupported() {
+ fmt.Println("System tray not supported")
+ // Fallback to window-only mode
+}
+```
+
+### Icon Looks Wrong on macOS
+
+**Cause:** Not using template icon
+
+**Solution:**
+
+```go
+// Use template icon
+systray.SetTemplateIcon(iconBytes)
+
+// Or design icon as template (black + transparent)
+```
+
+### Menu Not Updating
+
+**Cause:** Forgot to call `menu.Update()`
+
+**Solution:**
+
+```go
+menuItem.SetLabel("New Label")
+menu.Update() // Add this!
+```
+
+## Next Steps
+
+
+
+ Complete reference for menu item types and properties.
+
+ [Learn More →](/features/menus/reference)
+
+
+
+ Create application menu bars.
+
+ [Learn More →](/features/menus/application)
+
+
+
+ Create right-click context menus.
+
+ [Learn More →](/features/menus/context)
+
+
+
+ Build a complete system tray application.
+
+ [Learn More →](/tutorials/system-tray)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [system tray examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/systray-basic).
diff --git a/docs/src/content/docs/learn/notifications.mdx b/docs/src/content/docs/features/notifications/overview.mdx
similarity index 99%
rename from docs/src/content/docs/learn/notifications.mdx
rename to docs/src/content/docs/features/notifications/overview.mdx
index 6cbb8da07..efacdd74f 100644
--- a/docs/src/content/docs/learn/notifications.mdx
+++ b/docs/src/content/docs/features/notifications/overview.mdx
@@ -1,5 +1,8 @@
---
title: Notifications
+description: Display native system notifications with action buttons and text input
+sidebar:
+ order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
diff --git a/docs/src/content/docs/learn/dock.mdx b/docs/src/content/docs/features/platform/dock.mdx
similarity index 98%
rename from docs/src/content/docs/learn/dock.mdx
rename to docs/src/content/docs/features/platform/dock.mdx
index 76f385726..2638f7bf2 100644
--- a/docs/src/content/docs/learn/dock.mdx
+++ b/docs/src/content/docs/features/platform/dock.mdx
@@ -1,5 +1,8 @@
---
-title: Dock
+title: Dock & Taskbar
+description: Manage dock icon visibility and display badges on macOS and Windows
+sidebar:
+ order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
diff --git a/docs/src/content/docs/features/screens/info.mdx b/docs/src/content/docs/features/screens/info.mdx
new file mode 100644
index 000000000..7a7abf5f7
--- /dev/null
+++ b/docs/src/content/docs/features/screens/info.mdx
@@ -0,0 +1,466 @@
+---
+title: Screen Information
+description: Get information about displays and monitors
+sidebar:
+ order: 1
+---
+
+import { Card, CardGrid } from "@astrojs/starlight/components";
+
+## Screen Information
+
+Wails provides a **unified screen API** that works across all platforms. Get screen information, detect multiple monitors, query screen properties (size, position, DPI), identify the primary display, and handle DPI scaling with consistent code.
+
+## Quick Start
+
+```go
+// Get all screens
+screens := app.Screens.GetAll()
+
+for _, screen := range screens {
+ fmt.Printf("Screen: %s (%dx%d)\n",
+ screen.Name, screen.Width, screen.Height)
+}
+
+// Get primary screen
+primary := app.Screens.GetPrimary()
+fmt.Printf("Primary: %s\n", primary.Name)
+```
+
+**That's it!** Cross-platform screen information.
+
+## Getting Screen Information
+
+### All Screens
+
+```go
+screens := app.Screens.GetAll()
+
+for _, screen := range screens {
+ fmt.Printf("ID: %s\n", screen.ID)
+ fmt.Printf("Name: %s\n", screen.Name)
+ fmt.Printf("Size: %dx%d\n", screen.Width, screen.Height)
+ fmt.Printf("Position: %d,%d\n", screen.X, screen.Y)
+ fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
+ fmt.Printf("Primary: %v\n", screen.IsPrimary)
+ fmt.Println("---")
+}
+```
+
+### Primary Screen
+
+```go
+primary := app.Screens.GetPrimary()
+
+fmt.Printf("Primary screen: %s\n", primary.Name)
+fmt.Printf("Resolution: %dx%d\n", primary.Width, primary.Height)
+fmt.Printf("Scale factor: %.2f\n", primary.ScaleFactor)
+```
+
+### Current Screen
+
+Get the screen containing a window:
+
+```go
+screen := app.Screens.GetCurrent(window)
+
+fmt.Printf("Window is on: %s\n", screen.Name)
+```
+
+### Screen by ID
+
+```go
+screen := app.Screens.GetByID("screen-id")
+if screen != nil {
+ fmt.Printf("Found screen: %s\n", screen.Name)
+}
+```
+
+## Screen Properties
+
+### Screen Structure
+
+```go
+type Screen struct {
+ ID string // Unique identifier
+ Name string // Display name
+ X int // X position
+ Y int // Y position
+ Width int // Width in pixels
+ Height int // Height in pixels
+ ScaleFactor float32 // DPI scale (1.0, 1.5, 2.0, etc.)
+ IsPrimary bool // Is this the primary screen?
+}
+```
+
+### Physical vs Logical Pixels
+
+```go
+screen := app.Screens.GetPrimary()
+
+// Logical pixels (what you use)
+logicalWidth := screen.Width
+logicalHeight := screen.Height
+
+// Physical pixels (actual display)
+physicalWidth := int(float32(screen.Width) * screen.ScaleFactor)
+physicalHeight := int(float32(screen.Height) * screen.ScaleFactor)
+
+fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight)
+fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight)
+fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
+```
+
+**Common scale factors:**
+- `1.0` - Standard DPI (96 DPI)
+- `1.25` - 125% scaling (120 DPI)
+- `1.5` - 150% scaling (144 DPI)
+- `2.0` - 200% scaling (192 DPI) - Retina
+- `3.0` - 300% scaling (288 DPI) - 4K/5K
+
+## Window Positioning
+
+### Centre on Screen
+
+```go
+func centreOnScreen(window *application.WebviewWindow, screen *Screen) {
+ windowWidth, windowHeight := window.Size()
+
+ x := screen.X + (screen.Width-windowWidth)/2
+ y := screen.Y + (screen.Height-windowHeight)/2
+
+ window.SetPosition(x, y)
+}
+```
+
+### Position on Specific Screen
+
+```go
+func moveToScreen(window *application.WebviewWindow, screenIndex int) {
+ screens := app.Screens.GetAll()
+
+ if screenIndex < 0 || screenIndex >= len(screens) {
+ return
+ }
+
+ screen := screens[screenIndex]
+
+ // Centre on target screen
+ centreOnScreen(window, screen)
+}
+```
+
+### Position Relative to Screen
+
+```go
+// Top-left corner
+func positionTopLeft(window *application.WebviewWindow, screen *Screen) {
+ window.SetPosition(screen.X+10, screen.Y+10)
+}
+
+// Top-right corner
+func positionTopRight(window *application.WebviewWindow, screen *Screen) {
+ windowWidth, _ := window.Size()
+ window.SetPosition(screen.X+screen.Width-windowWidth-10, screen.Y+10)
+}
+
+// Bottom-right corner
+func positionBottomRight(window *application.WebviewWindow, screen *Screen) {
+ windowWidth, windowHeight := window.Size()
+ window.SetPosition(
+ screen.X+screen.Width-windowWidth-10,
+ screen.Y+screen.Height-windowHeight-10,
+ )
+}
+```
+
+## Multi-Monitor Support
+
+### Detect Multiple Monitors
+
+```go
+func hasMultipleMonitors() bool {
+ return len(app.Screens.GetAll()) > 1
+}
+
+func getMonitorCount() int {
+ return len(app.Screens.GetAll())
+}
+```
+
+### List All Monitors
+
+```go
+func listMonitors() {
+ screens := app.Screens.GetAll()
+
+ fmt.Printf("Found %d monitor(s):\n", len(screens))
+
+ for i, screen := range screens {
+ primary := ""
+ if screen.IsPrimary {
+ primary = " (Primary)"
+ }
+
+ fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary)
+ fmt.Printf(" Resolution: %dx%d\n", screen.Width, screen.Height)
+ fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y)
+ fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor)
+ }
+}
+```
+
+### Choose Monitor
+
+```go
+func chooseMonitor() (*Screen, error) {
+ screens := app.Screens.GetAll()
+
+ if len(screens) == 1 {
+ return screens[0], nil
+ }
+
+ // Show dialog to choose
+ var options []string
+ for i, screen := range screens {
+ primary := ""
+ if screen.IsPrimary {
+ primary = " (Primary)"
+ }
+ options = append(options,
+ fmt.Sprintf("%d. %s%s - %dx%d",
+ i+1, screen.Name, primary, screen.Width, screen.Height))
+ }
+
+ // Use dialog to select
+ // (Implementation depends on your dialog system)
+
+ return screens[0], nil
+}
+```
+
+## Complete Examples
+
+### Multi-Monitor Window Manager
+
+```go
+type MultiMonitorManager struct {
+ app *application.Application
+ windows map[int]*application.WebviewWindow
+}
+
+func NewMultiMonitorManager(app *application.Application) *MultiMonitorManager {
+ return &MultiMonitorManager{
+ app: app,
+ windows: make(map[int]*application.WebviewWindow),
+ }
+}
+
+func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error {
+ screens := m.app.Screens.GetAll()
+
+ if screenIndex < 0 || screenIndex >= len(screens) {
+ return errors.New("invalid screen index")
+ }
+
+ screen := screens[screenIndex]
+
+ // Create window
+ window := m.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: fmt.Sprintf("Window on %s", screen.Name),
+ Width: 800,
+ Height: 600,
+ })
+
+ // Centre on screen
+ x := screen.X + (screen.Width-800)/2
+ y := screen.Y + (screen.Height-600)/2
+ window.SetPosition(x, y)
+
+ window.Show()
+
+ m.windows[screenIndex] = window
+ return nil
+}
+
+func (m *MultiMonitorManager) CreateWindowOnEachScreen() {
+ screens := m.app.Screens.GetAll()
+
+ for i := range screens {
+ m.CreateWindowOnScreen(i)
+ }
+}
+```
+
+### Screen Change Detection
+
+```go
+type ScreenMonitor struct {
+ app *application.Application
+ lastScreens []*Screen
+ changeHandler func([]*Screen)
+}
+
+func NewScreenMonitor(app *application.Application) *ScreenMonitor {
+ return &ScreenMonitor{
+ app: app,
+ lastScreens: app.Screens.GetAll(),
+ }
+}
+
+func (sm *ScreenMonitor) OnScreenChange(handler func([]*Screen)) {
+ sm.changeHandler = handler
+}
+
+func (sm *ScreenMonitor) Start() {
+ ticker := time.NewTicker(2 * time.Second)
+
+ go func() {
+ for range ticker.C {
+ sm.checkScreens()
+ }
+ }()
+}
+
+func (sm *ScreenMonitor) checkScreens() {
+ current := sm.app.Screens.GetAll()
+
+ if len(current) != len(sm.lastScreens) {
+ sm.lastScreens = current
+ if sm.changeHandler != nil {
+ sm.changeHandler(current)
+ }
+ }
+}
+```
+
+### DPI-Aware Window Sizing
+
+```go
+func createDPIAwareWindow(screen *Screen) *application.WebviewWindow {
+ // Base size at 1.0 scale
+ baseWidth := 800
+ baseHeight := 600
+
+ // Adjust for DPI
+ width := int(float32(baseWidth) * screen.ScaleFactor)
+ height := int(float32(baseHeight) * screen.ScaleFactor)
+
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "DPI-Aware Window",
+ Width: width,
+ Height: height,
+ })
+
+ // Centre on screen
+ x := screen.X + (screen.Width-width)/2
+ y := screen.Y + (screen.Height-height)/2
+ window.SetPosition(x, y)
+
+ return window
+}
+```
+
+### Screen Layout Visualiser
+
+```go
+func visualiseScreenLayout() string {
+ screens := app.Screens.GetAll()
+
+ var layout strings.Builder
+ layout.WriteString("Screen Layout:\n\n")
+
+ for i, screen := range screens {
+ primary := ""
+ if screen.IsPrimary {
+ primary = " [PRIMARY]"
+ }
+
+ layout.WriteString(fmt.Sprintf("Screen %d: %s%s\n", i+1, screen.Name, primary))
+ layout.WriteString(fmt.Sprintf(" Position: (%d, %d)\n", screen.X, screen.Y))
+ layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Width, screen.Height))
+ layout.WriteString(fmt.Sprintf(" Scale: %.2fx\n", screen.ScaleFactor))
+ layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n",
+ int(float32(screen.Width)*screen.ScaleFactor),
+ int(float32(screen.Height)*screen.ScaleFactor)))
+ layout.WriteString("\n")
+ }
+
+ return layout.String()
+}
+```
+
+## Best Practices
+
+### ✅ Do
+
+- **Check screen count** - Handle single and multiple monitors
+- **Use logical pixels** - Wails handles DPI automatically
+- **Centre windows** - Better UX than fixed positions
+- **Validate positions** - Ensure windows are visible
+- **Handle screen changes** - Monitors can be added/removed
+- **Test on different DPI** - 100%, 125%, 150%, 200%
+
+### ❌ Don't
+
+- **Don't hardcode positions** - Use screen dimensions
+- **Don't assume primary screen** - User might have multiple
+- **Don't ignore scale factor** - Important for DPI awareness
+- **Don't position off-screen** - Validate coordinates
+- **Don't forget screen changes** - Laptops dock/undock
+- **Don't use physical pixels** - Use logical pixels
+
+## Platform Differences
+
+### macOS
+
+- Retina displays (2x scale factor)
+- Multiple displays common
+- Coordinate system: (0,0) at bottom-left
+- Spaces (virtual desktops) affect positioning
+
+### Windows
+
+- Various DPI scaling (100%, 125%, 150%, 200%)
+- Multiple displays common
+- Coordinate system: (0,0) at top-left
+- Per-monitor DPI awareness
+
+### Linux
+
+- Varies by desktop environment
+- X11 vs Wayland differences
+- DPI scaling support varies
+- Multiple displays supported
+
+## Next Steps
+
+
+
+ Learn about window management.
+
+ [Learn More →](/features/windows/basics)
+
+
+
+ Configure window appearance.
+
+ [Learn More →](/features/windows/options)
+
+
+
+ Multi-window patterns.
+
+ [Learn More →](/features/windows/multiple)
+
+
+
+ Call Go functions from JavaScript.
+
+ [Learn More →](/features/bindings/methods)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [screen examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
diff --git a/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx
new file mode 100644
index 000000000..007c62c52
--- /dev/null
+++ b/docs/src/content/docs/features/windows/basics.mdx
@@ -0,0 +1,607 @@
+---
+title: Window Basics
+description: Creating and managing application windows in Wails
+sidebar:
+ order: 1
+---
+
+import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
+
+## Window Management
+
+Wails provides a **unified window management API** that works across all platforms. Create windows, control their behaviour, and manage multiple windows with full control over creation, appearance, behaviour, and lifecycle.
+
+## Quick Start
+
+```go
+package main
+
+import "github.com/wailsapp/wails/v3/pkg/application"
+
+func main() {
+ app := application.New(application.Options{
+ Name: "My App",
+ })
+
+ // Create a window
+ window := app.NewWebviewWindow()
+
+ // Configure it
+ window.SetTitle("Hello Wails")
+ window.SetSize(800, 600)
+ window.Center()
+
+ // Show it
+ window.Show()
+
+ app.Run()
+}
+```
+
+**That's it!** You have a cross-platform window.
+
+## Creating Windows
+
+### Basic Window
+
+The simplest way to create a window:
+
+```go
+window := app.NewWebviewWindow()
+```
+
+**What you get:**
+- Default size (800x600)
+- Default title (application name)
+- WebView ready for your frontend
+- Platform-native appearance
+
+### Window with Options
+
+Create a window with custom configuration:
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "My Application",
+ Width: 1200,
+ Height: 800,
+ X: 100, // Position from left
+ Y: 100, // Position from top
+ AlwaysOnTop: false,
+ Frameless: false,
+ Hidden: false,
+ MinWidth: 400,
+ MinHeight: 300,
+ MaxWidth: 1920,
+ MaxHeight: 1080,
+})
+```
+
+**Common options:**
+
+| Option | Type | Description |
+|--------|------|-------------|
+| `Title` | `string` | Window title |
+| `Width` | `int` | Window width in pixels |
+| `Height` | `int` | Window height in pixels |
+| `X` | `int` | X position (from left) |
+| `Y` | `int` | Y position (from top) |
+| `AlwaysOnTop` | `bool` | Keep window above others |
+| `Frameless` | `bool` | Remove title bar and borders |
+| `Hidden` | `bool` | Start hidden |
+| `MinWidth` | `int` | Minimum width |
+| `MinHeight` | `int` | Minimum height |
+| `MaxWidth` | `int` | Maximum width |
+| `MaxHeight` | `int` | Maximum height |
+
+**See [Window Options](/features/windows/options) for complete list.**
+
+### Named Windows
+
+Give windows names for easy retrieval:
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "main-window",
+ Title: "Main Application",
+})
+
+// Later, find it by name
+mainWindow := app.GetWindowByName("main-window")
+if mainWindow != nil {
+ mainWindow.Show()
+}
+```
+
+**Use cases:**
+- Multiple windows (main, settings, about)
+- Finding windows from different parts of your code
+- Window communication
+
+## Controlling Windows
+
+### Show and Hide
+
+```go
+// Show window
+window.Show()
+
+// Hide window
+window.Hide()
+
+// Check if visible
+if window.IsVisible() {
+ fmt.Println("Window is visible")
+}
+```
+
+**Use cases:**
+- Splash screens (show, then hide)
+- Settings windows (hide when not needed)
+- Popup windows (show on demand)
+
+### Position and Size
+
+```go
+// Set size
+window.SetSize(1024, 768)
+
+// Set position
+window.SetPosition(100, 100)
+
+// Centre on screen
+window.Center()
+
+// Get current size
+width, height := window.Size()
+
+// Get current position
+x, y := window.Position()
+```
+
+**Coordinate system:**
+- (0, 0) is top-left of primary screen
+- Positive X goes right
+- Positive Y goes down
+
+### Window State
+
+```go
+// Minimise
+window.Minimise()
+
+// Maximise
+window.Maximise()
+
+// Fullscreen
+window.Fullscreen()
+
+// Restore to normal
+window.Restore()
+
+// Check state
+if window.IsMinimised() {
+ fmt.Println("Window is minimised")
+}
+
+if window.IsMaximised() {
+ fmt.Println("Window is maximised")
+}
+
+if window.IsFullscreen() {
+ fmt.Println("Window is fullscreen")
+}
+```
+
+**State transitions:**
+
+```
+Normal ←→ Minimised
+Normal ←→ Maximised
+Normal ←→ Fullscreen
+```
+
+### Title and Appearance
+
+```go
+// Set title
+window.SetTitle("My Application - Document.txt")
+
+// Set background colour
+window.SetBackgroundColour(0, 0, 0, 255) // RGBA
+
+// Set always on top
+window.SetAlwaysOnTop(true)
+
+// Set resizable
+window.SetResizable(false)
+```
+
+### Closing Windows
+
+```go
+// Close window
+window.Close()
+
+// Destroy window (force close)
+window.Destroy()
+```
+
+**Difference:**
+- `Close()` - Triggers close event, can be cancelled
+- `Destroy()` - Immediate destruction, cannot be cancelled
+
+## Finding Windows
+
+### By Name
+
+```go
+window := app.GetWindowByName("settings")
+if window != nil {
+ window.Show()
+}
+```
+
+### By ID
+
+Every window has a unique ID:
+
+```go
+id := window.ID()
+fmt.Printf("Window ID: %d\n", id)
+
+// Find by ID
+foundWindow := app.GetWindowByID(id)
+```
+
+### Current Window
+
+Get the currently focused window:
+
+```go
+current := app.CurrentWindow()
+if current != nil {
+ current.SetTitle("Active Window")
+}
+```
+
+### All Windows
+
+Get all windows:
+
+```go
+windows := app.GetAllWindows()
+fmt.Printf("Total windows: %d\n", len(windows))
+
+for _, w := range windows {
+ fmt.Printf("Window: %s (ID: %d)\n", w.Name(), w.ID())
+}
+```
+
+## Window Lifecycle
+
+### Creation
+
+```go
+app.OnWindowCreation(func(window *application.WebviewWindow) {
+ fmt.Printf("Window created: %s\n", window.Name())
+
+ // Configure new windows
+ window.SetMinSize(400, 300)
+})
+```
+
+### Closing
+
+```go
+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:** `OnClose` only works for user-initiated closes (clicking X button). It doesn't prevent `window.Destroy()`.
+
+### Destruction
+
+```go
+window.OnDestroy(func() {
+ fmt.Println("Window destroyed")
+ // Cleanup resources
+})
+```
+
+## Multiple Windows
+
+### Creating Multiple Windows
+
+```go
+// Main window
+mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "main",
+ Title: "Main Application",
+ Width: 1200,
+ Height: 800,
+})
+
+// Settings window
+settingsWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings",
+ Title: "Settings",
+ Width: 600,
+ Height: 400,
+ Hidden: true, // Start hidden
+})
+
+// Show settings when needed
+settingsWindow.Show()
+```
+
+### Window Communication
+
+Windows can communicate via events:
+
+```go
+// In main window
+app.EmitEvent("data-updated", map[string]interface{}{
+ "value": 42,
+})
+
+// In settings window
+app.OnEvent("data-updated", func(event *application.WailsEvent) {
+ data := event.Data.(map[string]interface{})
+ value := data["value"].(int)
+ fmt.Printf("Received: %d\n", value)
+})
+```
+
+**See [Events](/features/events/system) for more.**
+
+### Parent-Child Windows
+
+```go
+// Create child window
+childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Child Window",
+ Parent: mainWindow, // Set parent
+})
+```
+
+**Behaviour:**
+- Child closes when parent closes
+- Child stays above parent (on some platforms)
+- Child minimises with parent (on some platforms)
+
+**Platform support:**
+- **macOS:** Full support
+- **Windows:** Partial support
+- **Linux:** Varies by desktop environment
+
+## Platform-Specific Features
+
+
+
+ **Windows-specific features:**
+
+ ```go
+ // Flash taskbar button
+ window.Flash(true) // Start flashing
+ window.Flash(false) // Stop flashing
+
+ // Trigger Windows 11 Snap Assist (Win+Z)
+ window.SnapAssist()
+
+ // Set window icon
+ window.SetIcon(iconBytes)
+ ```
+
+ **Snap Assist:**
+ Shows Windows 11 snap layout options for the window.
+
+ **Taskbar flashing:**
+ Useful for notifications when window is minimised.
+
+
+
+ **macOS-specific features:**
+
+ ```go
+ // Transparent title bar
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Mac: application.MacOptions{
+ TitleBarAppearsTransparent: true,
+ Backdrop: application.MacBackdropTranslucent,
+ },
+ })
+ ```
+
+ **Backdrop types:**
+ - `MacBackdropNormal` - Standard window
+ - `MacBackdropTranslucent` - Translucent background
+ - `MacBackdropTransparent` - Fully transparent
+
+ **Native fullscreen:**
+ macOS fullscreen creates a new Space (virtual desktop).
+
+
+
+ **Linux-specific features:**
+
+ ```go
+ // Set window icon
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Linux: application.LinuxOptions{
+ Icon: iconBytes,
+ },
+ })
+ ```
+
+ **Desktop environment notes:**
+ - GNOME: Full support
+ - KDE Plasma: Full support
+ - XFCE: Partial support
+ - Others: Varies
+
+ **Window managers:**
+ - Tiling WMs may ignore size/position
+ - Some WMs don't support always-on-top
+
+
+
+## Common Patterns
+
+### Splash Screen
+
+```go
+// Create splash screen
+splash := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Loading...",
+ Width: 400,
+ Height: 300,
+ Frameless: true,
+ AlwaysOnTop: true,
+})
+
+// Show splash
+splash.Show()
+
+// Initialise application
+time.Sleep(2 * time.Second)
+
+// Hide splash, show main window
+splash.Close()
+mainWindow.Show()
+```
+
+### Settings Window
+
+```go
+var settingsWindow *application.WebviewWindow
+
+func showSettings() {
+ if settingsWindow == nil {
+ settingsWindow = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings",
+ Title: "Settings",
+ Width: 600,
+ Height: 400,
+ })
+ }
+
+ settingsWindow.Show()
+ settingsWindow.SetFocus()
+}
+```
+
+### Confirm Before Close
+
+```go
+window.OnClose(func() bool {
+ if hasUnsavedChanges() {
+ // Show dialog
+ result := showConfirmdialog("Unsaved changes. Close anyway?")
+ return result == "yes"
+ }
+ return true
+})
+```
+
+## Best Practices
+
+### ✅ Do
+
+- **Name important windows** - Easier to find later
+- **Set minimum size** - Prevent unusable layouts
+- **Centre windows** - Better UX than random position
+- **Handle close events** - Prevent data loss
+- **Test on all platforms** - Behaviour varies
+- **Use appropriate sizes** - Consider different screen sizes
+
+### ❌ Don't
+
+- **Don't create too many windows** - Confusing for users
+- **Don't forget to close windows** - Memory leaks
+- **Don't hardcode positions** - Different screen sizes
+- **Don't ignore platform differences** - Test thoroughly
+- **Don't block the UI thread** - Use goroutines for long operations
+
+## Troubleshooting
+
+### Window Not Showing
+
+**Possible causes:**
+1. Window created as hidden
+2. Window off-screen
+3. Window behind other windows
+
+**Solution:**
+
+```go
+window.Show()
+window.Center()
+window.SetFocus()
+```
+
+### Window Wrong Size
+
+**Cause:** DPI scaling on Windows/Linux
+
+**Solution:**
+
+```go
+// Wails handles DPI automatically
+// Just use logical pixels
+window.SetSize(800, 600)
+```
+
+### Window Closes Immediately
+
+**Cause:** Application exits when last window closes
+
+**Solution:**
+
+```go
+app := application.New(application.Options{
+ Mac: application.MacOptions{
+ ApplicationShouldTerminateAfterLastWindowClosed: false,
+ },
+})
+```
+
+## Next Steps
+
+
+
+ Complete reference for all window options.
+
+ [Learn More →](/features/windows/options)
+
+
+
+ Patterns for multi-window applications.
+
+ [Learn More →](/features/windows/multiple)
+
+
+
+ Create custom window chrome.
+
+ [Learn More →](/features/windows/frameless)
+
+
+
+ Handle window lifecycle events.
+
+ [Learn More →](/features/windows/events)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [window examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
diff --git a/docs/src/content/docs/features/windows/events.mdx b/docs/src/content/docs/features/windows/events.mdx
new file mode 100644
index 000000000..8d3ba317d
--- /dev/null
+++ b/docs/src/content/docs/features/windows/events.mdx
@@ -0,0 +1,693 @@
+---
+title: Window Events
+description: Handle window lifecycle and state change events
+sidebar:
+ order: 5
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+
+## 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.
+
+## Lifecycle Events
+
+### OnCreate
+
+Called when a window is created:
+
+```go
+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
+
+### OnClose
+
+Called when user attempts to close window:
+
+```go
+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:**
+
+```go
+window.OnClose(func() bool {
+ if !hasUnsavedChanges() {
+ return true
+ }
+
+ // Show confirmation dialog
+ dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Confirm Close",
+ Width: 400,
+ Height: 150,
+ Parent: window,
+ AlwaysOnTop: true,
+ })
+
+ // Wait for user response
+ result := waitFordialogResult(dialog)
+
+ return result == "yes"
+})
+```
+
+### OnDestroy
+
+Called when window is destroyed:
+
+```go
+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:**
+
+```go
+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
+ })
+}
+```
+
+## Focus Events
+
+### OnFocus
+
+Called when window gains focus:
+
+```go
+window.OnFocus(func() {
+ fmt.Println("Window gained focus")
+
+ // Update UI
+ updateTitleBar(true)
+
+ // Refresh data
+ refreshContent()
+
+ // Notify other windows
+ app.EmitEvent("window-focused", window.ID())
+})
+```
+
+**Use cases:**
+- Update UI appearance
+- Refresh data
+- Resume operations
+- Coordinate with other windows
+
+### OnBlur
+
+Called when window loses focus:
+
+```go
+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:**
+
+```go
+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")
+ }
+}
+```
+
+## State Change Events
+
+### OnMinimise / OnUnMinimise
+
+Called when window is minimised or restored:
+
+```go
+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
+
+### OnMaximise / OnUnMaximise
+
+Called when window is maximised or restored:
+
+```go
+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
+
+### OnFullscreen / OnUnFullscreen
+
+Called when window enters or exits fullscreen:
+
+```go
+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
+
+## Position and Size Events
+
+### OnMove
+
+Called when window is moved:
+
+```go
+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
+
+### OnResize
+
+Called when window is resized:
+
+```go
+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:**
+
+```go
+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)
+})
+```
+
+## Complete Example
+
+Here's a production-ready window with full event handling:
+
+```go
+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.NewWebviewWindowWithOptions(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")
+}
+```
+
+## Event Coordination
+
+### Cross-Window Events
+
+Coordinate between multiple windows:
+
+```go
+// In main window
+mainWindow.OnFocus(func() {
+ // Notify all windows
+ app.EmitEvent("main-window-focused", nil)
+})
+
+// In other windows
+app.OnEvent("main-window-focused", func(event *application.WailsEvent) {
+ // Update UI
+ updateRelativeToMain()
+})
+```
+
+### Event Chains
+
+Chain events together:
+
+```go
+window.OnMaximise(func() {
+ // Save state
+ saveWindowState()
+
+ // Update layout
+ window.EmitEvent("layout-changed", "maximised")
+
+ // Notify other windows
+ app.EmitEvent("window-maximised", window.ID())
+})
+```
+
+### Debounced Events
+
+Debounce frequent events:
+
+```go
+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)
+ })
+})
+```
+
+## Best Practices
+
+### ✅ Do
+
+- **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
+
+- **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
+
+## Troubleshooting
+
+### OnClose Not Firing
+
+**Cause:** Using `window.Destroy()` instead of `window.Close()`
+
+**Solution:**
+
+```go
+// ✅ Triggers OnClose
+window.Close()
+
+// ❌ Doesn't trigger OnClose
+window.Destroy()
+```
+
+### Events Not Firing
+
+**Cause:** Handler registered after event occurred
+
+**Solution:**
+
+```go
+// Register handlers immediately after creation
+window := app.NewWebviewWindow()
+window.OnClose(func() bool { return true })
+```
+
+### Memory Leaks
+
+**Cause:** Not cleaning up in OnDestroy
+
+**Solution:**
+
+```go
+window.OnDestroy(func() {
+ // Always cleanup
+ closeResources()
+ removeReferences()
+})
+```
+
+## Next Steps
+
+**Window Basics** - Learn the fundamentals of window management
+[Learn More →](/features/windows/basics)
+
+**Multiple Windows** - Patterns for multi-window applications
+[Learn More →](/features/windows/multiple)
+
+**Events System** - Deep dive into the event system
+[Learn More →](/features/events/system)
+
+**Application Lifecycle** - Understand the application lifecycle
+[Learn More →](/concepts/lifecycle)
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
diff --git a/docs/src/content/docs/features/windows/frameless.mdx b/docs/src/content/docs/features/windows/frameless.mdx
new file mode 100644
index 000000000..4c6b266f8
--- /dev/null
+++ b/docs/src/content/docs/features/windows/frameless.mdx
@@ -0,0 +1,870 @@
+---
+title: Frameless Windows
+description: Create custom window chrome with frameless windows
+sidebar:
+ order: 4
+---
+
+import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
+
+## Frameless Windows
+
+Wails provides **frameless window support** with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls.
+
+## Quick Start
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Frameless App",
+ Width: 800,
+ Height: 600,
+ Frameless: true,
+})
+```
+
+**CSS for draggable title bar:**
+
+```css
+.titlebar {
+ --wails-draggable: drag;
+ height: 40px;
+ background: #333;
+}
+
+.titlebar button {
+ --wails-draggable: no-drag;
+}
+```
+
+**HTML:**
+
+```html
+
+ My Application
+
+
+```
+
+**That's it!** You have a custom title bar.
+
+## Creating Frameless Windows
+
+### Basic Frameless Window
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ Width: 800,
+ Height: 600,
+})
+```
+
+**What you get:**
+- No title bar
+- No window borders
+- No system buttons
+- Transparent background (optional)
+
+**What you need to implement:**
+- Draggable area
+- Close/minimise/maximise buttons
+- Resize handles (if resizable)
+
+### With Transparent Background
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ BackgroundType: application.BackgroundTypeTransparent,
+})
+```
+
+**Use cases:**
+- Rounded corners
+- Custom shapes
+- Overlay windows
+- Splash screens
+
+## Drag Regions
+
+### CSS-Based Dragging
+
+Use the `--wails-draggable` CSS property:
+
+```css
+/* Draggable area */
+.titlebar {
+ --wails-draggable: drag;
+}
+
+/* Non-draggable elements within draggable area */
+.titlebar button {
+ --wails-draggable: no-drag;
+}
+```
+
+**Values:**
+- `drag` - Area is draggable
+- `no-drag` - Area is not draggable (even if parent is)
+
+### Complete Title Bar Example
+
+```html
+
+
My Application
+
+
+
+
+
+
+```
+
+```css
+.titlebar {
+ --wails-draggable: drag;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 40px;
+ background: #2c2c2c;
+ color: white;
+ padding: 0 16px;
+}
+
+.title {
+ font-size: 14px;
+ user-select: none;
+}
+
+.controls {
+ display: flex;
+ gap: 8px;
+}
+
+.controls button {
+ --wails-draggable: no-drag;
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ color: white;
+ font-size: 16px;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.controls button:hover {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.controls .close:hover {
+ background: #e81123;
+}
+```
+
+**JavaScript for buttons:**
+
+```javascript
+import { WindowMinimise, WindowMaximise, WindowClose } from '@wailsio/runtime'
+
+document.querySelector('.minimize').addEventListener('click', WindowMinimise)
+document.querySelector('.maximize').addEventListener('click', WindowMaximise)
+document.querySelector('.close').addEventListener('click', WindowClose)
+```
+
+## System Buttons
+
+### Implementing Close/Minimise/Maximise
+
+**Go side:**
+
+```go
+type WindowControls struct {
+ window *application.WebviewWindow
+}
+
+func (wc *WindowControls) Minimise() {
+ wc.window.Minimise()
+}
+
+func (wc *WindowControls) Maximise() {
+ if wc.window.IsMaximised() {
+ wc.window.UnMaximise()
+ } else {
+ wc.window.Maximise()
+ }
+}
+
+func (wc *WindowControls) Close() {
+ wc.window.Close()
+}
+```
+
+**JavaScript side:**
+
+```javascript
+import { Minimise, Maximise, Close } from './bindings/WindowControls'
+
+document.querySelector('.minimize').addEventListener('click', Minimise)
+document.querySelector('.maximize').addEventListener('click', Maximise)
+document.querySelector('.close').addEventListener('click', Close)
+```
+
+**Or use runtime methods:**
+
+```javascript
+import {
+ WindowMinimise,
+ WindowMaximise,
+ WindowClose
+} from '@wailsio/runtime'
+
+document.querySelector('.minimize').addEventListener('click', WindowMinimise)
+document.querySelector('.maximize').addEventListener('click', WindowMaximise)
+document.querySelector('.close').addEventListener('click', WindowClose)
+```
+
+### Toggle Maximise State
+
+Track maximise state for button icon:
+
+```javascript
+import { WindowIsMaximised, WindowMaximise, WindowUnMaximise } from '@wailsio/runtime'
+
+async function toggleMaximise() {
+ const isMaximised = await WindowIsMaximised()
+
+ if (isMaximised) {
+ await WindowUnMaximise()
+ } else {
+ await WindowMaximise()
+ }
+
+ updateMaximiseButton()
+}
+
+async function updateMaximiseButton() {
+ const isMaximised = await WindowIsMaximised()
+ const button = document.querySelector('.maximize')
+ button.textContent = isMaximised ? '❐' : '□'
+}
+```
+
+## Resize Handles
+
+### CSS-Based Resize
+
+Wails provides automatic resize handles for frameless windows:
+
+```css
+/* Enable resize on all edges */
+body {
+ --wails-resize: all;
+}
+
+/* Or specific edges */
+.resize-top {
+ --wails-resize: top;
+}
+
+.resize-bottom {
+ --wails-resize: bottom;
+}
+
+.resize-left {
+ --wails-resize: left;
+}
+
+.resize-right {
+ --wails-resize: right;
+}
+
+/* Corners */
+.resize-top-left {
+ --wails-resize: top-left;
+}
+
+.resize-top-right {
+ --wails-resize: top-right;
+}
+
+.resize-bottom-left {
+ --wails-resize: bottom-left;
+}
+
+.resize-bottom-right {
+ --wails-resize: bottom-right;
+}
+```
+
+**Values:**
+- `all` - Resize from all edges
+- `top`, `bottom`, `left`, `right` - Specific edges
+- `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners
+- `none` - No resize
+
+### Resize Handle Example
+
+```html
+
+```
+
+```css
+.resize-handle {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+}
+
+.resize-bottom-right {
+ --wails-resize: bottom-right;
+ bottom: 0;
+ right: 0;
+ cursor: nwse-resize;
+}
+```
+
+## Platform-Specific Behaviour
+
+
+
+ **Windows frameless windows:**
+
+ ```go
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ Windows: application.WindowsOptions{
+ DisableFramelessWindowDecorations: false,
+ },
+ })
+ ```
+
+ **Features:**
+ - Automatic drop shadow
+ - Snap layouts support (Windows 11)
+ - Aero Snap support
+ - DPI scaling
+
+ **Disable decorations:**
+ ```go
+ Windows: application.WindowsOptions{
+ DisableFramelessWindowDecorations: true,
+ },
+ ```
+
+ **Snap Assist:**
+ ```go
+ // Trigger Windows 11 Snap Assist
+ window.SnapAssist()
+ ```
+
+ **Custom title bar height:**
+ Windows automatically detects drag regions from CSS.
+
+
+
+ **macOS frameless windows:**
+
+ ```go
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ Mac: application.MacOptions{
+ TitleBarAppearsTransparent: true,
+ InvisibleTitleBarHeight: 40,
+ },
+ })
+ ```
+
+ **Features:**
+ - Native fullscreen support
+ - Traffic light buttons (optional)
+ - Vibrancy effects
+ - Transparent title bar
+
+ **Hide traffic lights:**
+ ```go
+ Mac: application.MacOptions{
+ TitleBarStyle: application.MacTitleBarStyleHidden,
+ },
+ ```
+
+ **Invisible title bar:**
+ Allows dragging whilst hiding the title bar:
+ ```go
+ Mac: application.MacOptions{
+ InvisibleTitleBarHeight: 40,
+ },
+ ```
+
+
+
+ **Linux frameless windows:**
+
+ ```go
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ })
+ ```
+
+ **Features:**
+ - Basic frameless support
+ - CSS drag regions
+ - Varies by desktop environment
+
+ **Desktop environment notes:**
+ - **GNOME:** Good support
+ - **KDE Plasma:** Good support
+ - **XFCE:** Basic support
+ - **Tiling WMs:** Limited support
+
+ **Compositor required:**
+ Transparency requires a compositor (most modern DEs have one).
+
+
+
+## Common Patterns
+
+### Pattern 1: Modern Title Bar
+
+```html
+
+
+

+
+
My Application
+
+
+
+
+
+
+```
+
+```css
+.modern-titlebar {
+ --wails-draggable: drag;
+ display: flex;
+ align-items: center;
+ height: 40px;
+ background: linear-gradient(to bottom, #3a3a3a, #2c2c2c);
+ border-bottom: 1px solid #1a1a1a;
+ padding: 0 16px;
+}
+
+.app-icon {
+ --wails-draggable: no-drag;
+ width: 24px;
+ height: 24px;
+ margin-right: 12px;
+}
+
+.title {
+ flex: 1;
+ font-size: 13px;
+ color: #e0e0e0;
+ user-select: none;
+}
+
+.controls {
+ display: flex;
+ gap: 1px;
+}
+
+.controls button {
+ --wails-draggable: no-drag;
+ width: 46px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ color: #e0e0e0;
+ font-size: 14px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.controls button:hover {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.controls .close:hover {
+ background: #e81123;
+ color: white;
+}
+```
+
+### Pattern 2: Splash Screen
+
+```go
+splash := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Loading...",
+ Width: 400,
+ Height: 300,
+ Frameless: true,
+ AlwaysOnTop: true,
+ BackgroundType: application.BackgroundTypeTransparent,
+})
+```
+
+```css
+body {
+ background: transparent;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.splash {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+ padding: 40px;
+ text-align: center;
+}
+```
+
+### Pattern 3: Rounded Window
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ BackgroundType: application.BackgroundTypeTransparent,
+})
+```
+
+```css
+body {
+ background: transparent;
+ margin: 8px;
+}
+
+.window {
+ background: white;
+ border-radius: 16px;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
+ overflow: hidden;
+ height: calc(100vh - 16px);
+}
+
+.titlebar {
+ --wails-draggable: drag;
+ background: #f5f5f5;
+ border-bottom: 1px solid #e0e0e0;
+}
+```
+
+### Pattern 4: Overlay Window
+
+```go
+overlay := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Frameless: true,
+ AlwaysOnTop: true,
+ BackgroundType: application.BackgroundTypeTransparent,
+})
+```
+
+```css
+body {
+ background: transparent;
+}
+
+.overlay {
+ background: rgba(0, 0, 0, 0.8);
+ backdrop-filter: blur(10px);
+ border-radius: 8px;
+ padding: 20px;
+}
+```
+
+## Complete Example
+
+Here's a production-ready frameless window:
+
+**Go:**
+
+```go
+package main
+
+import (
+ _ "embed"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+//go:embed frontend/dist
+var assets embed.FS
+
+func main() {
+ app := application.New(application.Options{
+ Name: "Frameless App",
+ })
+
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Frameless Application",
+ Width: 1000,
+ Height: 700,
+ MinWidth: 800,
+ MinHeight: 600,
+ Frameless: true,
+
+ Assets: application.AssetOptions{
+ Handler: application.AssetFileServerFS(assets),
+ },
+
+ Mac: application.MacOptions{
+ TitleBarAppearsTransparent: true,
+ InvisibleTitleBarHeight: 40,
+ },
+
+ Windows: application.WindowsOptions{
+ DisableFramelessWindowDecorations: false,
+ },
+ })
+
+ window.Center()
+ window.Show()
+
+ app.Run()
+}
+```
+
+**HTML:**
+
+```html
+
+
+
+
+
+
+
+
+
Frameless Application
+
+
+
+
+
+
+
+
Hello from Frameless Window!
+
+
+
+
+
+```
+
+**CSS:**
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background: #f5f5f5;
+}
+
+.window {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.titlebar {
+ --wails-draggable: drag;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 40px;
+ background: #ffffff;
+ border-bottom: 1px solid #e0e0e0;
+ padding: 0 16px;
+}
+
+.title {
+ font-size: 13px;
+ font-weight: 500;
+ color: #333;
+ user-select: none;
+}
+
+.controls {
+ display: flex;
+ gap: 8px;
+}
+
+.controls button {
+ --wails-draggable: no-drag;
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ color: #666;
+ font-size: 16px;
+ cursor: pointer;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
+}
+
+.controls button:hover {
+ background: #f0f0f0;
+ color: #333;
+}
+
+.controls .close:hover {
+ background: #e81123;
+ color: white;
+}
+
+.content {
+ flex: 1;
+ padding: 40px;
+ overflow: auto;
+}
+```
+
+**JavaScript:**
+
+```javascript
+import {
+ WindowMinimise,
+ WindowMaximise,
+ WindowUnMaximise,
+ WindowIsMaximised,
+ WindowClose
+} from '@wailsio/runtime'
+
+// Minimise button
+document.querySelector('.minimize').addEventListener('click', () => {
+ WindowMinimise()
+})
+
+// Maximise/restore button
+const maximiseBtn = document.querySelector('.maximize')
+maximiseBtn.addEventListener('click', async () => {
+ const isMaximised = await WindowIsMaximised()
+
+ if (isMaximised) {
+ await WindowUnMaximise()
+ } else {
+ await WindowMaximise()
+ }
+
+ updateMaximiseButton()
+})
+
+// Close button
+document.querySelector('.close').addEventListener('click', () => {
+ WindowClose()
+})
+
+// Update maximise button icon
+async function updateMaximiseButton() {
+ const isMaximised = await WindowIsMaximised()
+ maximiseBtn.textContent = isMaximised ? '❐' : '□'
+ maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise'
+}
+
+// Initial state
+updateMaximiseButton()
+```
+
+## Best Practices
+
+### ✅ Do
+
+- **Provide draggable area** - Users need to move the window
+- **Implement system buttons** - Close, minimise, maximise
+- **Set minimum size** - Prevent unusable layouts
+- **Test on all platforms** - Behaviour varies
+- **Use CSS for drag regions** - Flexible and maintainable
+- **Provide visual feedback** - Hover states on buttons
+
+### ❌ Don't
+
+- **Don't forget resize handles** - If window is resizable
+- **Don't make entire window draggable** - Prevents interaction
+- **Don't forget no-drag on buttons** - They won't work
+- **Don't use tiny drag areas** - Hard to grab
+- **Don't forget platform differences** - Test thoroughly
+
+## Troubleshooting
+
+### Window Won't Drag
+
+**Cause:** Missing `--wails-draggable: drag`
+
+**Solution:**
+
+```css
+.titlebar {
+ --wails-draggable: drag;
+}
+```
+
+### Buttons Don't Work
+
+**Cause:** Buttons are in draggable area
+
+**Solution:**
+
+```css
+.titlebar button {
+ --wails-draggable: no-drag;
+}
+```
+
+### Can't Resize Window
+
+**Cause:** Missing resize handles
+
+**Solution:**
+
+```css
+body {
+ --wails-resize: all;
+}
+```
+
+## Next Steps
+
+
+
+ Learn the fundamentals of window management.
+
+ [Learn More →](/features/windows/basics)
+
+
+
+ Complete reference for window options.
+
+ [Learn More →](/features/windows/options)
+
+
+
+ Handle window lifecycle events.
+
+ [Learn More →](/features/windows/events)
+
+
+
+ Patterns for multi-window applications.
+
+ [Learn More →](/features/windows/multiple)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless).
diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx
new file mode 100644
index 000000000..3fa42abb7
--- /dev/null
+++ b/docs/src/content/docs/features/windows/multiple.mdx
@@ -0,0 +1,814 @@
+---
+title: Multiple Windows
+description: Patterns and best practices for multi-window applications
+sidebar:
+ order: 3
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+
+## Multi-Window Applications
+
+Wails v3 provides **native multi-window support** for creating settings windows, document windows, tool palettes, and inspector windows. Track windows, enable communication between them, and manage their lifecycle with simple, consistent APIs.
+
+### Main + Settings Window
+
+```go
+package main
+
+import "github.com/wailsapp/wails/v3/pkg/application"
+
+type App struct {
+ app *application.Application
+ mainWindow *application.WebviewWindow
+ settingsWindow *application.WebviewWindow
+}
+
+func main() {
+ app := &App{}
+
+ app.app = application.New(application.Options{
+ Name: "Multi-Window App",
+ })
+
+ // Create main window
+ app.mainWindow = app.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "main",
+ Title: "Main Application",
+ Width: 1200,
+ Height: 800,
+ })
+
+ // Create settings window (hidden initially)
+ app.settingsWindow = app.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings",
+ Title: "Settings",
+ Width: 600,
+ Height: 400,
+ Hidden: true,
+ })
+
+ app.app.Run()
+}
+
+// Show settings from main window
+func (a *App) ShowSettings() {
+ if a.settingsWindow != nil {
+ a.settingsWindow.Show()
+ a.settingsWindow.SetFocus()
+ }
+}
+```
+
+**Key points:**
+- Main window always visible
+- Settings window created but hidden
+- Show settings on demand
+- Reuse same window (don't create multiple)
+
+## Window Tracking
+
+### Get All Windows
+
+```go
+windows := app.GetAllWindows()
+fmt.Printf("Total windows: %d\n", len(windows))
+
+for _, window := range windows {
+ fmt.Printf("- %s (ID: %d)\n", window.Name(), window.ID())
+}
+```
+
+### Find Specific Window
+
+```go
+// By name
+settings := app.GetWindowByName("settings")
+if settings != nil {
+ settings.Show()
+}
+
+// By ID
+window := app.GetWindowByID(123)
+
+// Current (focused) window
+current := app.CurrentWindow()
+```
+
+### Window Registry Pattern
+
+Track windows in your application:
+
+```go
+type WindowManager struct {
+ windows map[string]*application.WebviewWindow
+ mu sync.RWMutex
+}
+
+func (wm *WindowManager) Register(name string, window *application.WebviewWindow) {
+ wm.mu.Lock()
+ defer wm.mu.Unlock()
+ wm.windows[name] = window
+}
+
+func (wm *WindowManager) Get(name string) *application.WebviewWindow {
+ wm.mu.RLock()
+ defer wm.mu.RUnlock()
+ return wm.windows[name]
+}
+
+func (wm *WindowManager) Remove(name string) {
+ wm.mu.Lock()
+ defer wm.mu.Unlock()
+ delete(wm.windows, name)
+}
+```
+
+## Window Communication
+
+### Using Events
+
+Windows communicate via the event system:
+
+```go
+// In main window - emit event
+app.EmitEvent("settings-changed", map[string]interface{}{
+ "theme": "dark",
+ "fontSize": 14,
+})
+
+// In settings window - listen for event
+app.OnEvent("settings-changed", func(event *application.WailsEvent) {
+ data := event.Data.(map[string]interface{})
+ theme := data["theme"].(string)
+ fontSize := data["fontSize"].(int)
+
+ // Update UI
+ updateSettings(theme, fontSize)
+})
+```
+
+### Shared State Pattern
+
+Use a shared state manager:
+
+```go
+type AppState struct {
+ theme string
+ fontSize int
+ mu sync.RWMutex
+}
+
+var state = &AppState{
+ theme: "light",
+ fontSize: 12,
+}
+
+func (s *AppState) SetTheme(theme string) {
+ s.mu.Lock()
+ s.theme = theme
+ s.mu.Unlock()
+
+ // Notify all windows
+ app.EmitEvent("theme-changed", theme)
+}
+
+func (s *AppState) GetTheme() string {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.theme
+}
+```
+
+### Window-to-Window Messages
+
+Send messages between specific windows:
+
+```go
+// Get target window
+targetWindow := app.GetWindowByName("preview")
+
+// Emit event to specific window
+targetWindow.EmitEvent("update-preview", previewData)
+```
+
+## Common Patterns
+
+### Pattern 1: Singleton Windows
+
+Ensure only one instance of a window exists:
+
+```go
+var settingsWindow *application.WebviewWindow
+
+func ShowSettings(app *application.Application) {
+ // Create if doesn't exist
+ if settingsWindow == nil {
+ settingsWindow = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings",
+ Title: "Settings",
+ Width: 600,
+ Height: 400,
+ })
+
+ // Cleanup on close
+ settingsWindow.OnDestroy(func() {
+ settingsWindow = nil
+ })
+ }
+
+ // Show and focus
+ settingsWindow.Show()
+ settingsWindow.SetFocus()
+}
+```
+
+### Pattern 2: Document Windows
+
+Multiple instances of the same window type:
+
+```go
+type DocumentWindow struct {
+ window *application.WebviewWindow
+ filePath string
+ modified bool
+}
+
+var documents = make(map[string]*DocumentWindow)
+
+func OpenDocument(app *application.Application, filePath string) {
+ // Check if already open
+ if doc, exists := documents[filePath]; exists {
+ doc.window.Show()
+ doc.window.SetFocus()
+ return
+ }
+
+ // Create new document window
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: filepath.Base(filePath),
+ Width: 800,
+ Height: 600,
+ })
+
+ doc := &DocumentWindow{
+ window: window,
+ filePath: filePath,
+ modified: false,
+ }
+
+ documents[filePath] = doc
+
+ // Cleanup on close
+ window.OnDestroy(func() {
+ delete(documents, filePath)
+ })
+
+ // Load document
+ loadDocument(window, filePath)
+}
+```
+
+### Pattern 3: Tool Palettes
+
+Floating windows that stay on top:
+
+```go
+func CreateToolPalette(app *application.Application) *application.WebviewWindow {
+ palette := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "tools",
+ Title: "Tools",
+ Width: 200,
+ Height: 400,
+ AlwaysOnTop: true,
+ Resizable: false,
+ })
+
+ return palette
+}
+```
+
+### Pattern 4: Modal dialogs
+
+Child windows that block parent:
+
+```go
+func ShowModaldialog(parent *application.WebviewWindow, title string) {
+ dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: title,
+ Width: 400,
+ Height: 200,
+ Parent: parent,
+ AlwaysOnTop: true,
+ Resizable: false,
+ })
+
+ // Disable parent (platform-specific)
+ parent.SetEnabled(false)
+
+ // Re-enable parent on close
+ dialog.OnDestroy(func() {
+ parent.SetEnabled(true)
+ parent.SetFocus()
+ })
+}
+```
+
+### Pattern 5: Inspector/Preview Windows
+
+Linked windows that update together:
+
+```go
+type EditorApp struct {
+ editor *application.WebviewWindow
+ preview *application.WebviewWindow
+}
+
+func (e *EditorApp) UpdatePreview(content string) {
+ if e.preview != nil && e.preview.IsVisible() {
+ e.preview.EmitEvent("content-changed", content)
+ }
+}
+
+func (e *EditorApp) TogglePreview() {
+ if e.preview == nil {
+ e.preview = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "preview",
+ Title: "Preview",
+ Width: 600,
+ Height: 800,
+ })
+
+ e.preview.OnDestroy(func() {
+ e.preview = nil
+ })
+ }
+
+ if e.preview.IsVisible() {
+ e.preview.Hide()
+ } else {
+ e.preview.Show()
+ }
+}
+```
+
+## Parent-Child Relationships
+
+### Creating Child Windows
+
+```go
+childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Child Window",
+ Parent: parentWindow,
+})
+```
+
+**Behaviour:**
+- Child closes when parent closes
+- Child stays above parent (on some platforms)
+- Child minimises with parent (on some platforms)
+
+**Platform support:**
+
+| Feature | macOS | Windows | Linux |
+|---------|-------|---------|-------|
+| Auto-close | ✅ | ✅ | ⚠️ Varies |
+| Stay above | ✅ | ⚠️ Partial | ⚠️ Varies |
+| Minimise together | ✅ | ❌ | ⚠️ Varies |
+
+### Modal Behaviour
+
+Create modal-like behaviour:
+
+```go
+func ShowModal(parent *application.WebviewWindow) {
+ modal := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Modal dialog",
+ Width: 400,
+ Height: 200,
+ Parent: parent,
+ AlwaysOnTop: true,
+ })
+
+ // Disable parent interaction
+ parent.SetEnabled(false)
+
+ // Re-enable on close
+ modal.OnClose(func() bool {
+ parent.SetEnabled(true)
+ parent.SetFocus()
+ return true
+ })
+}
+```
+
+**Note:** True modal behaviour (blocking) varies by platform.
+
+## Window Lifecycle Management
+
+### Creation Callbacks
+
+Be notified when windows are created:
+
+```go
+app.OnWindowCreation(func(window *application.WebviewWindow) {
+ fmt.Printf("Window created: %s\n", window.Name())
+
+ // Configure all new windows
+ window.SetMinSize(400, 300)
+})
+```
+
+### Destruction Callbacks
+
+Cleanup when windows are destroyed:
+
+```go
+window.OnDestroy(func() {
+ fmt.Printf("Window %s destroyed\n", window.Name())
+
+ // Cleanup resources
+ cleanup(window.ID())
+
+ // Remove from tracking
+ removeFromRegistry(window.Name())
+})
+```
+
+### Application Quit Behaviour
+
+Control when application quits:
+
+```go
+app := application.New(application.Options{
+ Mac: application.MacOptions{
+ // Don't quit when last window closes
+ ApplicationShouldTerminateAfterLastWindowClosed: false,
+ },
+})
+```
+
+**Use cases:**
+- System tray applications
+- Background services
+- Menu bar applications (macOS)
+
+## Memory Management
+
+### Preventing Leaks
+
+Always clean up window references:
+
+```go
+var windows = make(map[string]*application.WebviewWindow)
+
+func CreateWindow(name string) {
+ window := app.NewWebviewWindow()
+ windows[name] = window
+
+ // IMPORTANT: Clean up on destroy
+ window.OnDestroy(func() {
+ delete(windows, name)
+ })
+}
+```
+
+### Closing vs Destroying
+
+```go
+// Close - triggers OnClose, can be cancelled
+window.Close()
+
+// Destroy - immediate, cannot be cancelled
+window.Destroy()
+```
+
+**Best practice:** Use `Close()` for user-initiated closes, `Destroy()` for cleanup.
+
+### Resource Cleanup
+
+```go
+type ManagedWindow struct {
+ window *application.WebviewWindow
+ resources []io.Closer
+}
+
+func (mw *ManagedWindow) Destroy() {
+ // Close all resources
+ for _, resource := range mw.resources {
+ resource.Close()
+ }
+
+ // Destroy window
+ mw.window.Destroy()
+}
+```
+
+## Advanced Patterns
+
+### Window Pool
+
+Reuse windows instead of creating new ones:
+
+```go
+type WindowPool struct {
+ available []*application.WebviewWindow
+ inUse map[uint]*application.WebviewWindow
+ mu sync.Mutex
+}
+
+func (wp *WindowPool) Acquire() *application.WebviewWindow {
+ wp.mu.Lock()
+ defer wp.mu.Unlock()
+
+ // Reuse available window
+ if len(wp.available) > 0 {
+ window := wp.available[0]
+ wp.available = wp.available[1:]
+ wp.inUse[window.ID()] = window
+ return window
+ }
+
+ // Create new window
+ window := app.NewWebviewWindow()
+ wp.inUse[window.ID()] = window
+ return window
+}
+
+func (wp *WindowPool) Release(window *application.WebviewWindow) {
+ wp.mu.Lock()
+ defer wp.mu.Unlock()
+
+ delete(wp.inUse, window.ID())
+ window.Hide()
+ wp.available = append(wp.available, window)
+}
+```
+
+### Window Groups
+
+Manage related windows together:
+
+```go
+type WindowGroup struct {
+ name string
+ windows []*application.WebviewWindow
+}
+
+func (wg *WindowGroup) Add(window *application.WebviewWindow) {
+ wg.windows = append(wg.windows, window)
+}
+
+func (wg *WindowGroup) ShowAll() {
+ for _, window := range wg.windows {
+ window.Show()
+ }
+}
+
+func (wg *WindowGroup) HideAll() {
+ for _, window := range wg.windows {
+ window.Hide()
+ }
+}
+
+func (wg *WindowGroup) CloseAll() {
+ for _, window := range wg.windows {
+ window.Close()
+ }
+}
+```
+
+### Workspace Management
+
+Save and restore window layouts:
+
+```go
+type WindowLayout struct {
+ Windows []WindowState `json:"windows"`
+}
+
+type WindowState struct {
+ Name string `json:"name"`
+ X int `json:"x"`
+ Y int `json:"y"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+}
+
+func SaveLayout() *WindowLayout {
+ layout := &WindowLayout{}
+
+ for _, window := range app.GetAllWindows() {
+ x, y := window.Position()
+ width, height := window.Size()
+
+ layout.Windows = append(layout.Windows, WindowState{
+ Name: window.Name(),
+ X: x,
+ Y: y,
+ Width: width,
+ Height: height,
+ })
+ }
+
+ return layout
+}
+
+func RestoreLayout(layout *WindowLayout) {
+ for _, state := range layout.Windows {
+ window := app.GetWindowByName(state.Name)
+ if window != nil {
+ window.SetPosition(state.X, state.Y)
+ window.SetSize(state.Width, state.Height)
+ }
+ }
+}
+```
+
+## Complete Example
+
+Here's a production-ready multi-window application:
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "os"
+ "sync"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type MultiWindowApp struct {
+ app *application.Application
+ windows map[string]*application.WebviewWindow
+ mu sync.RWMutex
+}
+
+func main() {
+ mwa := &MultiWindowApp{
+ windows: make(map[string]*application.WebviewWindow),
+ }
+
+ mwa.app = application.New(application.Options{
+ Name: "Multi-Window Application",
+ Mac: application.MacOptions{
+ ApplicationShouldTerminateAfterLastWindowClosed: false,
+ },
+ })
+
+ // Create main window
+ mwa.CreateMainWindow()
+
+ // Load saved layout
+ mwa.LoadLayout()
+
+ mwa.app.Run()
+}
+
+func (mwa *MultiWindowApp) CreateMainWindow() {
+ window := mwa.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "main",
+ Title: "Main Application",
+ Width: 1200,
+ Height: 800,
+ })
+
+ mwa.RegisterWindow("main", window)
+}
+
+func (mwa *MultiWindowApp) ShowSettings() {
+ if window := mwa.GetWindow("settings"); window != nil {
+ window.Show()
+ window.SetFocus()
+ return
+ }
+
+ window := mwa.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings",
+ Title: "Settings",
+ Width: 600,
+ Height: 400,
+ })
+
+ mwa.RegisterWindow("settings", window)
+}
+
+func (mwa *MultiWindowApp) OpenDocument(path string) {
+ name := "doc-" + path
+
+ if window := mwa.GetWindow(name); window != nil {
+ window.Show()
+ window.SetFocus()
+ return
+ }
+
+ window := mwa.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: name,
+ Title: path,
+ Width: 800,
+ Height: 600,
+ })
+
+ mwa.RegisterWindow(name, window)
+}
+
+func (mwa *MultiWindowApp) RegisterWindow(name string, window *application.WebviewWindow) {
+ mwa.mu.Lock()
+ mwa.windows[name] = window
+ mwa.mu.Unlock()
+
+ window.OnDestroy(func() {
+ mwa.UnregisterWindow(name)
+ })
+}
+
+func (mwa *MultiWindowApp) UnregisterWindow(name string) {
+ mwa.mu.Lock()
+ delete(mwa.windows, name)
+ mwa.mu.Unlock()
+}
+
+func (mwa *MultiWindowApp) GetWindow(name string) *application.WebviewWindow {
+ mwa.mu.RLock()
+ defer mwa.mu.RUnlock()
+ return mwa.windows[name]
+}
+
+func (mwa *MultiWindowApp) SaveLayout() {
+ layout := make(map[string]WindowState)
+
+ mwa.mu.RLock()
+ for name, window := range mwa.windows {
+ x, y := window.Position()
+ width, height := window.Size()
+
+ layout[name] = WindowState{
+ X: x,
+ Y: y,
+ Width: width,
+ Height: height,
+ }
+ }
+ mwa.mu.RUnlock()
+
+ data, _ := json.Marshal(layout)
+ os.WriteFile("layout.json", data, 0644)
+}
+
+func (mwa *MultiWindowApp) LoadLayout() {
+ data, err := os.ReadFile("layout.json")
+ if err != nil {
+ return
+ }
+
+ var layout map[string]WindowState
+ if err := json.Unmarshal(data, &layout); err != nil {
+ return
+ }
+
+ for name, state := range layout {
+ if window := mwa.GetWindow(name); window != nil {
+ window.SetPosition(state.X, state.Y)
+ window.SetSize(state.Width, state.Height)
+ }
+ }
+}
+
+type WindowState struct {
+ X int `json:"x"`
+ Y int `json:"y"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+}
+```
+
+## Best Practices
+
+### ✅ Do
+
+- **Track windows** - Keep references for easy access
+- **Clean up on destroy** - Prevent memory leaks
+- **Use events for communication** - Decoupled architecture
+- **Reuse windows** - Don't create duplicates
+- **Save/restore layouts** - Better UX
+- **Handle window close** - Confirm before closing with unsaved data
+
+### ❌ Don't
+
+- **Don't create unlimited windows** - Memory and performance issues
+- **Don't forget to clean up** - Memory leaks
+- **Don't use global variables carelessly** - Thread-safety issues
+- **Don't block window creation** - Create asynchronously if needed
+- **Don't ignore platform differences** - Test on all platforms
+
+## Next Steps
+
+- [Window Basics](/features/windows/basics) - Learn the fundamentals of window management
+- [Window Events](/features/windows/events) - Handle window lifecycle events
+- [Events System](/features/events/system) - Deep dive into the event system
+- [Frameless Windows](/features/windows/frameless) - Create custom window chrome
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [multi-window example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/multi-window).
diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx
new file mode 100644
index 000000000..0b278e651
--- /dev/null
+++ b/docs/src/content/docs/features/windows/options.mdx
@@ -0,0 +1,852 @@
+---
+title: Window Options
+description: Complete reference for WebviewWindowOptions
+sidebar:
+ order: 2
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+
+## Window Configuration Options
+
+Wails provides comprehensive window configuration with dozens of options for size, position, appearance, and behaviour. This reference covers all available options across Windows, macOS, and Linux as the **complete reference** for `WebviewWindowOptions`. Every option, every platform, with examples and constraints.
+
+## WebviewWindowOptions Structure
+
+```go
+type WebviewWindowOptions struct {
+ // Identity
+ Name string
+ Title string
+
+ // Size and Position
+ Width int
+ Height int
+ X int
+ Y int
+ MinWidth int
+ MinHeight int
+ MaxWidth int
+ MaxHeight int
+
+ // Initial State
+ Hidden bool
+ Frameless bool
+ Resizable bool
+ AlwaysOnTop bool
+ Fullscreen bool
+ Minimised bool
+ Maximised bool
+ WindowState WindowState
+
+ // Appearance
+ BackgroundColour RGBA
+ BackgroundType BackgroundType
+
+ // Content
+ URL string
+ HTML string
+
+ // Assets
+ Assets application.AssetOptions
+
+ // Security
+ ContentProtectionEnabled bool
+
+ // Lifecycle
+ OnClose func() bool
+ OnDestroy func()
+
+ // Platform-Specific
+ Mac MacOptions
+ Windows WindowsOptions
+ Linux LinuxOptions
+}
+```
+
+## Core Options
+
+### Name
+
+**Type:** `string`
+**Default:** Auto-generated UUID
+**Platform:** All
+
+```go
+Name: "main-window"
+```
+
+**Purpose:** Unique identifier for finding windows later.
+
+**Best practices:**
+- Use descriptive names: `"main"`, `"settings"`, `"about"`
+- Use kebab-case: `"file-browser"`, `"color-picker"`
+- Keep it short and memorable
+
+**Example:**
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings-window",
+})
+
+// Later...
+settings := app.GetWindowByName("settings-window")
+```
+
+### Title
+
+**Type:** `string`
+**Default:** Application name
+**Platform:** All
+
+```go
+Title: "My Application"
+```
+
+**Purpose:** Text shown in title bar and taskbar.
+
+**Dynamic updates:**
+
+```go
+window.SetTitle("My Application - Document.txt")
+```
+
+### Width / Height
+
+**Type:** `int` (pixels)
+**Default:** 800 x 600
+**Platform:** All
+**Constraints:** Must be positive
+
+```go
+Width: 1200,
+Height: 800,
+```
+
+**Purpose:** Initial window size in logical pixels.
+
+**Notes:**
+- Wails handles DPI scaling automatically
+- Use logical pixels, not physical pixels
+- Consider minimum screen resolution (1024x768)
+
+**Example sizes:**
+
+| Use Case | Width | Height |
+|----------|-------|--------|
+| Small utility | 400 | 300 |
+| Standard app | 1024 | 768 |
+| Large app | 1440 | 900 |
+| Full HD | 1920 | 1080 |
+
+### X / Y
+
+**Type:** `int` (pixels)
+**Default:** Centred on screen
+**Platform:** All
+
+```go
+X: 100, // 100px from left edge
+Y: 100, // 100px from top edge
+```
+
+**Purpose:** Initial window position.
+
+**Coordinate system:**
+- (0, 0) is top-left of primary screen
+- Positive X goes right
+- Positive Y goes down
+
+**Best practice:** Use `Center()` instead:
+
+```go
+window := app.NewWebviewWindow()
+window.Center()
+```
+
+### MinWidth / MinHeight
+
+**Type:** `int` (pixels)
+**Default:** 0 (no minimum)
+**Platform:** All
+
+```go
+MinWidth: 400,
+MinHeight: 300,
+```
+
+**Purpose:** Prevent window from being too small.
+
+**Use cases:**
+- Prevent broken layouts
+- Ensure usability
+- Maintain aspect ratio
+
+**Example:**
+
+```go
+// Prevent window smaller than 400x300
+MinWidth: 400,
+MinHeight: 300,
+```
+
+### MaxWidth / MaxHeight
+
+**Type:** `int` (pixels)
+**Default:** 0 (no maximum)
+**Platform:** All
+
+```go
+MaxWidth: 1920,
+MaxHeight: 1080,
+```
+
+**Purpose:** Prevent window from being too large.
+
+**Use cases:**
+- Fixed-size applications
+- Prevent excessive resource usage
+- Maintain design constraints
+
+## State Options
+
+### Hidden
+
+**Type:** `bool`
+**Default:** `false`
+**Platform:** All
+
+```go
+Hidden: true,
+```
+
+**Purpose:** Create window without showing it.
+
+**Use cases:**
+- Background windows
+- Windows shown on demand
+- Splash screens (create, load, then show)
+- Prevent white flash while loading content
+
+**Platform improvements:**
+- **Windows:** Fixed white window flash - window stays invisible until `Show()` is called
+- **macOS:** Full support
+- **Linux:** Full support
+
+**Recommended pattern for smooth loading:**
+
+```go
+// Create hidden window
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "main-window",
+ Hidden: true,
+ BackgroundColour: application.RGBA{R: 30, G: 30, B: 30, A: 255}, // Match your theme
+})
+
+// Load content while hidden
+// ... content loads ...
+
+// Show when ready (no flash!)
+window.Show()
+```
+
+**Example:**
+
+```go
+settings := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Name: "settings",
+ Hidden: true,
+})
+
+// Show when needed
+settings.Show()
+```
+
+### Frameless
+
+**Type:** `bool`
+**Default:** `false`
+**Platform:** All
+
+```go
+Frameless: true,
+```
+
+**Purpose:** Remove title bar and window borders.
+
+**Use cases:**
+- Custom window chrome
+- Splash screens
+- Kiosk applications
+- Custom-designed windows
+
+**Important:** You'll need to implement:
+- Window dragging
+- Close/minimise/maximise buttons
+- Resize handles (if resizable)
+
+**See [Frameless Windows](/features/windows/frameless) for details.**
+
+### Resizable
+
+**Type:** `bool`
+**Default:** `true`
+**Platform:** All
+
+```go
+Resizable: false,
+```
+
+**Purpose:** Allow/prevent window resizing.
+
+**Use cases:**
+- Fixed-size applications
+- Splash screens
+- dialogs
+
+**Note:** Users can still maximise/fullscreen unless you prevent those too.
+
+### AlwaysOnTop
+
+**Type:** `bool`
+**Default:** `false`
+**Platform:** All
+
+```go
+AlwaysOnTop: true,
+```
+
+**Purpose:** Keep window above all other windows.
+
+**Use cases:**
+- Floating toolbars
+- Notifications
+- Picture-in-picture
+- Timers
+
+**Platform notes:**
+- **macOS:** Full support
+- **Windows:** Full support
+- **Linux:** Depends on window manager
+
+### Fullscreen
+
+**Type:** `bool`
+**Default:** `false`
+**Platform:** All
+
+```go
+Fullscreen: true,
+```
+
+**Purpose:** Start in fullscreen mode.
+
+**Platform behaviour:**
+- **macOS:** Creates new Space (virtual desktop)
+- **Windows:** Covers taskbar
+- **Linux:** Varies by desktop environment
+
+**Toggle at runtime:**
+
+```go
+window.Fullscreen()
+window.UnFullscreen()
+```
+
+### Minimised / Maximised
+
+**Type:** `bool`
+**Default:** `false`
+**Platform:** All
+
+```go
+Minimised: true, // Start minimised
+Maximised: true, // Start maximised
+```
+
+**Purpose:** Set initial window state.
+
+**Note:** Don't set both to `true` - behaviour is undefined.
+
+### WindowState
+
+**Type:** `WindowState` enum
+**Default:** `WindowStateNormal`
+**Platform:** All
+
+```go
+WindowState: application.WindowStateMaximised,
+```
+
+**Values:**
+- `WindowStateNormal` - Normal window
+- `WindowStateMinimised` - Minimised
+- `WindowStateMaximised` - Maximised
+- `WindowStateFullscreen` - Fullscreen
+- `WindowStateHidden` - Hidden
+
+**Preferred over individual flags:**
+
+```go
+// ✅ Preferred
+WindowState: application.WindowStateMaximised,
+
+// ❌ Avoid
+Maximised: true,
+```
+
+## Appearance Options
+
+### BackgroundColour
+
+**Type:** `RGBA` struct
+**Default:** White (255, 255, 255, 255)
+**Platform:** All
+
+```go
+BackgroundColour: application.RGBA{R: 0, G: 0, H: 0, A: 255},
+```
+
+**Purpose:** Window background colour before content loads.
+
+**Use cases:**
+- Match your app's theme
+- Prevent white flash on dark themes
+- Smooth loading experience
+
+**Example:**
+
+```go
+// Dark theme
+BackgroundColour: application.RGBA{R: 30, G: 30, B: 30, A: 255},
+
+// Light theme
+BackgroundColour: application.RGBA{R: 255, G: 255, B: 255, A: 255},
+```
+
+**Helper method:**
+
+```go
+window.SetBackgroundColour(30, 30, 30, 255)
+```
+
+### BackgroundType
+
+**Type:** `BackgroundType` enum
+**Default:** `BackgroundTypeSolid`
+**Platform:** macOS, Windows (partial)
+
+```go
+BackgroundType: application.BackgroundTypeTranslucent,
+```
+
+**Values:**
+- `BackgroundTypeSolid` - Solid colour
+- `BackgroundTypeTransparent` - Fully transparent
+- `BackgroundTypeTranslucent` - Semi-transparent blur
+
+**Platform support:**
+- **macOS:** All types supported
+- **Windows:** Transparent and Translucent (Windows 11+)
+- **Linux:** Solid only
+
+**Example (macOS):**
+
+```go
+BackgroundType: application.BackgroundTypeTranslucent,
+Mac: application.MacOptions{
+ Backdrop: application.MacBackdropTranslucent,
+},
+```
+
+## Content Options
+
+### URL
+
+**Type:** `string`
+**Default:** Empty (loads from Assets)
+**Platform:** All
+
+```go
+URL: "https://example.com",
+```
+
+**Purpose:** Load external URL instead of embedded assets.
+
+**Use cases:**
+- Development (load from dev server)
+- Web-based applications
+- Hybrid applications
+
+**Example:**
+
+```go
+// Development
+URL: "http://localhost:5173",
+
+// Production
+Assets: application.AssetOptions{
+ Handler: application.AssetFileServerFS(assets),
+},
+```
+
+### HTML
+
+**Type:** `string`
+**Default:** Empty
+**Platform:** All
+
+```go
+HTML: "
Hello World
",
+```
+
+**Purpose:** Load HTML string directly.
+
+**Use cases:**
+- Simple windows
+- Generated content
+- Testing
+
+**Example:**
+
+```go
+HTML: `
+
+
+
Simple Window
+
Hello from Wails!
+
+`,
+```
+
+### Assets
+
+**Type:** `AssetOptions`
+**Default:** Inherited from application
+**Platform:** All
+
+```go
+Assets: application.AssetOptions{
+ Handler: application.AssetFileServerFS(assets),
+},
+```
+
+**Purpose:** Serve embedded frontend assets.
+
+**See [Build System](/concepts/build-system) for details.**
+
+## Security Options
+
+### ContentProtectionEnabled
+
+**Type:** `bool`
+**Default:** `false`
+**Platform:** Windows (10+), macOS
+
+```go
+ContentProtectionEnabled: true,
+```
+
+**Purpose:** Prevent screen capture of window contents.
+
+**Platform support:**
+- **Windows:** Windows 10 build 19041+ (full), older versions (partial)
+- **macOS:** Full support
+- **Linux:** Not supported
+
+**Use cases:**
+- Banking applications
+- Password managers
+- Medical records
+- Confidential documents
+
+**Important notes:**
+1. Doesn't prevent physical photography
+2. Some tools may bypass protection
+3. Part of comprehensive security, not sole protection
+4. DevTools windows not protected automatically
+
+**Example:**
+
+```go
+window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Secure Window",
+ ContentProtectionEnabled: true,
+})
+
+// Toggle at runtime
+window.SetContentProtection(true)
+```
+
+## Lifecycle Options
+
+### OnClose
+
+**Type:** `func() bool`
+**Default:** `nil` (always allow close)
+**Platform:** All
+
+```go
+OnClose: func() bool {
+ // Return false to cancel close
+ // Return true to allow close
+ return true
+},
+```
+
+**Purpose:** Handle window close events, optionally prevent closing.
+
+**Use cases:**
+- Confirm before closing with unsaved changes
+- Save state before closing
+- Prevent accidental closure
+
+**Example:**
+
+```go
+OnClose: func() bool {
+ if hasUnsavedChanges() {
+ result := showConfirmdialog("Unsaved changes. Close anyway?")
+ return result == "yes"
+ }
+ return true
+},
+```
+
+**Important:** Only triggered by user actions (clicking X), not `window.Destroy()`.
+
+### OnDestroy
+
+**Type:** `func()`
+**Default:** `nil`
+**Platform:** All
+
+```go
+OnDestroy: func() {
+ // Cleanup code
+ fmt.Println("Window destroyed")
+},
+```
+
+**Purpose:** Cleanup when window is destroyed.
+
+**Use cases:**
+- Release resources
+- Close connections
+- Update application state
+
+**Example:**
+
+```go
+OnDestroy: func() {
+ // Close database connection
+ if db != nil {
+ db.Close()
+ }
+
+ // Remove from window list
+ removeWindow(window.ID())
+},
+```
+
+## Platform-Specific Options
+
+### Mac Options
+
+```go
+Mac: application.MacOptions{
+ TitleBarAppearsTransparent: true,
+ Backdrop: application.MacBackdropTranslucent,
+ InvisibleTitleBarHeight: 50,
+ TitleBarStyle: application.MacTitleBarStyleHidden,
+},
+```
+
+**TitleBarAppearsTransparent** (`bool`)
+- Makes title bar transparent
+- Content extends into title bar area
+
+**Backdrop** (`MacBackdrop`)
+- `MacBackdropNormal` - Standard
+- `MacBackdropTranslucent` - Blurred translucent
+- `MacBackdropTransparent` - Fully transparent
+
+**InvisibleTitleBarHeight** (`int`)
+- Height of invisible title bar (for dragging)
+- Only when `TitleBarStyle` is `MacTitleBarStyleHidden`
+
+**TitleBarStyle** (`MacTitleBarStyle`)
+- `MacTitleBarStyleDefault` - Standard title bar
+- `MacTitleBarStyleHidden` - Hidden title bar
+- `MacTitleBarStyleHiddenInset` - Hidden with inset
+
+**Example:**
+
+```go
+Mac: application.MacOptions{
+ TitleBarAppearsTransparent: true,
+ Backdrop: application.MacBackdropTranslucent,
+ InvisibleTitleBarHeight: 50,
+},
+```
+
+### Windows Options
+
+```go
+Windows: application.WindowsOptions{
+ DisableWindowIcon: false,
+ WindowBackdropType: application.WindowsBackdropTypeAuto,
+ CustomTheme: nil,
+ DisableFramelessWindowDecorations: false,
+},
+```
+
+**DisableWindowIcon** (`bool`)
+- Remove icon from title bar
+- Cleaner appearance
+
+**WindowBackdropType** (`WindowsBackdropType`)
+- `WindowsBackdropTypeAuto` - System default
+- `WindowsBackdropTypeNone` - No backdrop
+- `WindowsBackdropTypeMica` - Mica material (Windows 11)
+- `WindowsBackdropTypeAcrylic` - Acrylic material (Windows 11)
+- `WindowsBackdropTypeTabbed` - Tabbed material (Windows 11)
+
+**CustomTheme** (`*WindowsTheme`)
+- Custom colour theme
+- Override system colours
+
+**DisableFramelessWindowDecorations** (`bool`)
+- Disable default frameless decorations
+- For custom window chrome
+
+**Example:**
+
+```go
+Windows: application.WindowsOptions{
+ WindowBackdropType: application.WindowsBackdropTypeMica,
+ DisableWindowIcon: true,
+},
+```
+
+### Linux Options
+
+```go
+Linux: application.LinuxOptions{
+ Icon: []byte{/* PNG data */},
+ WindowIsTranslucent: false,
+},
+```
+
+**Icon** (`[]byte`)
+- Window icon (PNG format)
+- Shown in title bar and taskbar
+
+**WindowIsTranslucent** (`bool`)
+- Enable window translucency
+- Requires compositor support
+
+**Example:**
+
+```go
+//go:embed icon.png
+var icon []byte
+
+Linux: application.LinuxOptions{
+ Icon: icon,
+},
+```
+
+## Complete Example
+
+Here's a production-ready window configuration:
+
+```go
+package main
+
+import (
+ _ "embed"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+//go:embed frontend/dist
+var assets embed.FS
+
+//go:embed icon.png
+var icon []byte
+
+func main() {
+ app := application.New(application.Options{
+ Name: "My Application",
+ })
+
+ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ // Identity
+ Name: "main-window",
+ Title: "My Application",
+
+ // Size and Position
+ Width: 1200,
+ Height: 800,
+ MinWidth: 800,
+ MinHeight: 600,
+
+ // Initial State
+ WindowState: application.WindowStateNormal,
+ Resizable: true,
+
+ // Appearance
+ BackgroundColour: application.RGBA{R: 255, G: 255, B: 255, A: 255},
+
+ // Content
+ Assets: application.AssetOptions{
+ Handler: application.AssetFileServerFS(assets),
+ },
+
+ // Lifecycle
+ OnClose: func() bool {
+ if hasUnsavedChanges() {
+ result := showConfirmdialog("Unsaved changes. Close anyway?")
+ return result == "yes"
+ }
+ return true
+ },
+
+ OnDestroy: func() {
+ cleanup()
+ },
+
+ // Platform-Specific
+ Mac: application.MacOptions{
+ TitleBarAppearsTransparent: true,
+ Backdrop: application.MacBackdropTranslucent,
+ },
+
+ Windows: application.WindowsOptions{
+ WindowBackdropType: application.WindowsBackdropTypeMica,
+ DisableWindowIcon: false,
+ },
+
+ Linux: application.LinuxOptions{
+ Icon: icon,
+ },
+ })
+
+ window.Center()
+ window.Show()
+
+ app.Run()
+}
+```
+
+## Next Steps
+
+- [Window Basics](/features/windows/basics) - Creating and controlling windows
+- [Multiple Windows](/features/windows/multiple) - Multi-window patterns
+- [Frameless Windows](/features/windows/frameless) - Custom window chrome
+- [Window Events](/features/windows/events) - Lifecycle events
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
diff --git a/docs/src/content/docs/getting-started/your-first-app.mdx b/docs/src/content/docs/getting-started/your-first-app.mdx
index fa0978683..143e66256 100644
--- a/docs/src/content/docs/getting-started/your-first-app.mdx
+++ b/docs/src/content/docs/getting-started/your-first-app.mdx
@@ -14,10 +14,7 @@ import wails_build from '../../../assets/wails_build.mp4';
import wails_dev from '../../../assets/wails_dev.mp4';
-Creating your first application with Wails v3 is an exciting journey into
-the world of modern desktop app development. This guide will walk you through
-the process of creating a basic application, showcasing the power and simplicity
-of Wails.
+This guide shows you how to create your first Wails v3 application, covering project setup, building, and development workflow.
diff --git a/docs/src/content/docs/guides/architecture.mdx b/docs/src/content/docs/guides/architecture.mdx
new file mode 100644
index 000000000..72678befd
--- /dev/null
+++ b/docs/src/content/docs/guides/architecture.mdx
@@ -0,0 +1,199 @@
+---
+title: Architecture Patterns
+description: Design patterns for Wails applications
+sidebar:
+ order: 7
+---
+
+## Overview
+
+Proven patterns for organising your Wails application.
+
+## Service Layer Pattern
+
+### Structure
+
+```
+app/
+├── main.go
+├── services/
+│ ├── user_service.go
+│ ├── data_service.go
+│ └── file_service.go
+└── models/
+ └── user.go
+```
+
+### Implementation
+
+```go
+// Service interface
+type UserService interface {
+ Create(email, password string) (*User, error)
+ GetByID(id int) (*User, error)
+ Update(user *User) error
+ Delete(id int) error
+}
+
+// Implementation
+type userService struct {
+ app *application.Application
+ db *sql.DB
+}
+
+func NewUserService(app *application.Application, db *sql.DB) UserService {
+ return &userService{app: app, db: db}
+}
+```
+
+## Repository Pattern
+
+### Structure
+
+```go
+// Repository interface
+type UserRepository interface {
+ Create(user *User) error
+ FindByID(id int) (*User, error)
+ Update(user *User) error
+ Delete(id int) error
+}
+
+// Service uses repository
+type UserService struct {
+ repo UserRepository
+}
+
+func (s *UserService) Create(email, password string) (*User, error) {
+ user := &User{Email: email}
+ return user, s.repo.Create(user)
+}
+```
+
+## Event-Driven Architecture
+
+### Event Bus
+
+```go
+type EventBus struct {
+ app *application.Application
+ listeners map[string][]func(interface{})
+ mu sync.RWMutex
+}
+
+func (eb *EventBus) Subscribe(event string, handler func(interface{})) {
+ eb.mu.Lock()
+ defer eb.mu.Unlock()
+ eb.listeners[event] = append(eb.listeners[event], handler)
+}
+
+func (eb *EventBus) Publish(event string, data interface{}) {
+ eb.mu.RLock()
+ handlers := eb.listeners[event]
+ eb.mu.RUnlock()
+
+ for _, handler := range handlers {
+ go handler(data)
+ }
+}
+```
+
+### Usage
+
+```go
+// Subscribe
+eventBus.Subscribe("user.created", func(data interface{}) {
+ user := data.(*User)
+ sendWelcomeEmail(user)
+})
+
+// Publish
+eventBus.Publish("user.created", user)
+```
+
+## Dependency Injection
+
+### Manual DI
+
+```go
+type App struct {
+ userService *UserService
+ fileService *FileService
+ db *sql.DB
+}
+
+func NewApp() *App {
+ db := openDatabase()
+
+ return &App{
+ db: db,
+ userService: NewUserService(db),
+ fileService: NewFileService(db),
+ }
+}
+```
+
+### Using Wire
+
+```go
+// wire.go
+//go:build wireinject
+
+func InitializeApp() (*App, error) {
+ wire.Build(
+ openDatabase,
+ NewUserService,
+ NewFileService,
+ NewApp,
+ )
+ return nil, nil
+}
+```
+
+## State Management
+
+### Centralised State
+
+```go
+type AppState struct {
+ currentUser *User
+ settings *Settings
+ mu sync.RWMutex
+}
+
+func (s *AppState) SetUser(user *User) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.currentUser = user
+}
+
+func (s *AppState) GetUser() *User {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.currentUser
+}
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Separate concerns
+- Use interfaces
+- Inject dependencies
+- Handle errors properly
+- Keep services focused
+- Document architecture
+
+### ❌ Don't
+
+- Don't create god objects
+- Don't tightly couple components
+- Don't skip error handling
+- Don't ignore concurrency
+- Don't over-engineer
+
+## Next Steps
+
+- [Security](/guides/security) - Security best practices
+- [Best Practices](/features/bindings/best-practices) - Bindings best practices
diff --git a/docs/src/content/docs/guides/auto-updates.mdx b/docs/src/content/docs/guides/auto-updates.mdx
new file mode 100644
index 000000000..685850413
--- /dev/null
+++ b/docs/src/content/docs/guides/auto-updates.mdx
@@ -0,0 +1,171 @@
+---
+title: Auto-Updates
+description: Implement automatic application updates
+sidebar:
+ order: 4
+---
+
+## Overview
+
+Keep your application up-to-date with automatic updates.
+
+## Update Strategies
+
+### 1. Check on Startup
+
+```go
+func (a *App) checkForUpdates() {
+ latest, err := getLatestVersion()
+ if err != nil {
+ return
+ }
+
+ if isNewer(latest, currentVersion) {
+ a.promptUpdate(latest)
+ }
+}
+```
+
+### 2. Periodic Checks
+
+```go
+func (a *App) startUpdateChecker() {
+ ticker := time.NewTicker(24 * time.Hour)
+ go func() {
+ for range ticker.C {
+ a.checkForUpdates()
+ }
+ }()
+}
+```
+
+### 3. Manual Check
+
+```go
+func (a *App) CheckForUpdates() {
+ result, _ := a.app.QuestionDialog().
+ SetMessage("Check for updates?").
+ SetButtons("Check", "Cancel").
+ Show()
+
+ if result == "Check" {
+ a.checkForUpdates()
+ }
+}
+```
+
+## Implementation
+
+### Version Checking
+
+```go
+type UpdateInfo struct {
+ Version string `json:"version"`
+ DownloadURL string `json:"download_url"`
+ ReleaseNotes string `json:"release_notes"`
+}
+
+func getLatestVersion() (*UpdateInfo, error) {
+ resp, err := http.Get("https://api.example.com/latest")
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var info UpdateInfo
+ if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
+```
+
+### Download and Install
+
+```go
+func (a *App) downloadUpdate(url string) error {
+ // Download update
+ resp, err := http.Get(url)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ // Save to temp file
+ tmpFile, err := os.CreateTemp("", "update-*.exe")
+ if err != nil {
+ return err
+ }
+ defer tmpFile.Close()
+
+ // Copy data
+ if _, err := io.Copy(tmpFile, resp.Body); err != nil {
+ return err
+ }
+
+ // Launch installer and quit
+ return a.installUpdate(tmpFile.Name())
+}
+```
+
+## Third-Party Solutions
+
+### Squirrel
+
+```go
+import "github.com/Squirrel/go-squirrel"
+
+func setupUpdater() {
+ updater := squirrel.NewUpdater(squirrel.Options{
+ URL: "https://updates.example.com",
+ })
+
+ updater.CheckForUpdates()
+}
+```
+
+### Self-Hosted
+
+Host update manifests on your own server:
+
+```json
+{
+ "version": "1.0.1",
+ "platforms": {
+ "windows": {
+ "url": "https://example.com/myapp-1.0.1-windows.exe",
+ "sha256": "..."
+ },
+ "darwin": {
+ "url": "https://example.com/myapp-1.0.1-macos.dmg",
+ "sha256": "..."
+ }
+ },
+ "release_notes": "Bug fixes and improvements"
+}
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Verify update signatures
+- Show release notes
+- Allow users to skip versions
+- Test update process thoroughly
+- Provide rollback mechanism
+- Use HTTPS for downloads
+
+### ❌ Don't
+
+- Don't force immediate updates
+- Don't skip signature verification
+- Don't interrupt user work
+- Don't forget error handling
+- Don't lose user data
+
+## Next Steps
+
+- [Creating Installers](/guides/installers) - Package your application
+- [Testing](/guides/testing) - Test your application
diff --git a/docs/src/content/docs/learn/build.mdx b/docs/src/content/docs/guides/build/customization.mdx
similarity index 98%
rename from docs/src/content/docs/learn/build.mdx
rename to docs/src/content/docs/guides/build/customization.mdx
index c3ace7ea4..041738ddf 100644
--- a/docs/src/content/docs/learn/build.mdx
+++ b/docs/src/content/docs/guides/build/customization.mdx
@@ -1,7 +1,8 @@
---
-title: Build System
+title: Build Customization
+description: Customize your build process using Task and Taskfile.yml
sidebar:
- order: 40
+ order: 1
---
import { FileTree } from "@astrojs/starlight/components";
diff --git a/docs/src/content/docs/guides/build/windows.mdx b/docs/src/content/docs/guides/build/windows.mdx
new file mode 100644
index 000000000..8e04ea0bd
--- /dev/null
+++ b/docs/src/content/docs/guides/build/windows.mdx
@@ -0,0 +1,415 @@
+---
+title: Windows Packaging
+description: Package your Wails application for Windows distribution
+sidebar:
+ order: 4
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+Learn how to package your Wails application for Windows distribution using various installer formats.
+
+## Overview
+
+Wails provides several packaging options for Windows:
+- **NSIS** - Nullsoft Scriptable Install System (recommended)
+- **MSI** - Windows Installer Package
+- **Portable** - Standalone executable (no installation)
+- **MSIX** - Modern Windows app package
+
+## NSIS Installer
+
+NSIS creates professional installers with customization options and automatic system integration.
+
+### Quick Start
+
+Build an NSIS installer:
+
+```bash
+wails3 build
+wails3 task nsis
+```
+
+This generates `build/bin/
---installer.exe`.
+
+### Features
+
+**Automatic Integration:**
+- ✅ Start Menu shortcuts
+- ✅ Desktop shortcuts (optional)
+- ✅ File associations
+- ✅ **Custom URL protocol registration** (NEW)
+- ✅ Uninstaller creation
+- ✅ Registry entries
+
+**Custom Protocols:**
+
+Wails automatically registers custom URL protocols defined in your application:
+
+```go
+app := application.New(application.Options{
+ Name: "My Application",
+ Protocols: []application.Protocol{
+ {
+ Scheme: "myapp",
+ Description: "My Application Protocol",
+ },
+ },
+})
+```
+
+The NSIS installer:
+1. Registers all protocols during installation
+2. Associates them with your application executable
+3. Removes them during uninstallation
+
+**No additional configuration needed!**
+
+### Customization
+
+Customize the installer via `wails.json`:
+
+```json
+{
+ "name": "MyApp",
+ "outputfilename": "myapp.exe",
+ "nsis": {
+ "companyName": "My Company",
+ "productName": "My Application",
+ "productDescription": "An amazing desktop application",
+ "productVersion": "1.0.0",
+ "installerIcon": "build/appicon.ico",
+ "license": "LICENSE.txt",
+ "allowInstallDirCustomization": true,
+ "installDirectory": "$PROGRAMFILES64\\MyApp",
+ "createDesktopShortcut": true,
+ "runAfterInstall": true,
+ "adminPrivileges": false
+ }
+}
+```
+
+### NSIS Options
+
+| Option | Type | Description | Default |
+|--------|------|-------------|---------|
+| `companyName` | string | Company name shown in installer | Project name |
+| `productName` | string | Product name | App name |
+| `productDescription` | string | Description shown in installer | App description |
+| `productVersion` | string | Version number (e.g., "1.0.0") | "1.0.0" |
+| `installerIcon` | string | Path to .ico file for installer | App icon |
+| `license` | string | Path to license file (txt) | None |
+| `allowInstallDirCustomization` | boolean | Let users choose install location | true |
+| `installDirectory` | string | Default installation directory | `$PROGRAMFILES64\` |
+| `createDesktopShortcut` | boolean | Create desktop shortcut | true |
+| `runAfterInstall` | boolean | Run app after installation | false |
+| `adminPrivileges` | boolean | Require admin rights to install | false |
+
+### Advanced NSIS
+
+#### Custom NSIS Script
+
+For advanced customization, provide your own NSIS script:
+
+```bash
+# Create custom template
+cp build/nsis/installer.nsi build/nsis/installer.custom.nsi
+
+# Edit build/nsis/installer.custom.nsi
+# Add custom sections, pages, or logic
+
+# Build with custom script
+wails3 task nsis --script build/nsis/installer.custom.nsi
+```
+
+#### Available Macros
+
+Wails provides NSIS macros for common tasks:
+
+**wails.associateCustomProtocols**
+- Registers all custom URL protocols defined in `application.Options.Protocols`
+- Called automatically during installation
+- Creates registry entries under `HKEY_CURRENT_USER\SOFTWARE\Classes\`
+
+**wails.unassociateCustomProtocols**
+- Removes custom URL protocol registrations
+- Called automatically during uninstallation
+- Cleans up all protocol-related registry entries
+
+**Example usage in custom NSIS script:**
+
+```nsis
+Section "Install"
+ # ... your installation code ...
+
+ # Register custom protocols
+ !insertmacro wails.associateCustomProtocols
+
+ # ... more installation code ...
+SectionEnd
+
+Section "Uninstall"
+ # Remove custom protocols
+ !insertmacro wails.unassociateCustomProtocols
+
+ # ... your uninstallation code ...
+SectionEnd
+```
+
+## MSI Installer
+
+Windows Installer Package format with Windows logo certification support.
+
+### Build MSI
+
+```bash
+wails3 build
+wails3 task msi
+```
+
+Generates `build/bin/--.msi`.
+
+### Customization
+
+Configure via `wails.json`:
+
+```json
+{
+ "msi": {
+ "productCode": "{GUID}",
+ "upgradeCode": "{GUID}",
+ "manufacturer": "My Company",
+ "installScope": "perMachine",
+ "shortcuts": {
+ "desktop": true,
+ "startMenu": true
+ }
+ }
+}
+```
+
+### MSI vs NSIS
+
+| Feature | NSIS | MSI |
+|---------|------|-----|
+| Customization | ✅ High | ⚠️ Limited |
+| File Size | ✅ Smaller | ⚠️ Larger |
+| Corporate Deployment | ⚠️ Less common | ✅ Preferred |
+| Custom UI | ✅ Full control | ⚠️ Restricted |
+| Windows Logo | ❌ No | ✅ Yes |
+| Protocol Registration | ✅ Automatic | ⚠️ Manual |
+
+**Use NSIS when:**
+- You want maximum customization
+- You need custom branding and UI
+- You want automatic protocol registration
+- File size matters
+
+**Use MSI when:**
+- You need Windows logo certification
+- You're deploying in enterprise environments
+- You need Group Policy support
+- You want Windows Update integration
+
+## Portable Executable
+
+Single executable with no installation required.
+
+### Build Portable
+
+```bash
+wails3 build
+```
+
+Output: `build/bin/.exe`
+
+### Characteristics
+
+- No installation required
+- No registry changes
+- No administrator privileges needed
+- Can run from USB drives
+- No automatic updates
+- No Start Menu integration
+
+### Use Cases
+
+- Trial versions
+- USB stick applications
+- Corporate environments with restricted installations
+- Quick testing and demos
+
+## MSIX Packages
+
+Modern Windows app package format for Microsoft Store and sideloading.
+
+See [MSIX Packaging Guide](/guides/build/msix) for detailed information.
+
+## Code Signing
+
+Sign your executables and installers to:
+- Avoid Windows SmartScreen warnings
+- Establish publisher identity
+- Enable automatic updates
+- Meet corporate security requirements
+
+See [Code Signing Guide](/guides/build/signing) for details.
+
+## Icon Requirements
+
+### Application Icon
+
+**Format:** `.ico` file with multiple resolutions
+
+**Recommended sizes:**
+- 16x16, 32x32, 48x48, 64x64, 128x128, 256x256
+
+**Create from PNG:**
+
+```bash
+# Using ImageMagick
+magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 appicon.ico
+
+# Using online tools
+# https://icoconvert.com/
+```
+
+### Installer Icon
+
+Same `.ico` file can be used for both application and installer.
+
+Configure in `wails.json`:
+
+```json
+{
+ "nsis": {
+ "installerIcon": "build/appicon.ico"
+ },
+ "windows": {
+ "applicationIcon": "build/appicon.ico"
+ }
+}
+```
+
+## Building for Different Architectures
+
+### AMD64 (64-bit)
+
+```bash
+wails3 build -platform windows/amd64
+```
+
+### ARM64
+
+```bash
+wails3 build -platform windows/arm64
+```
+
+### Universal Build
+
+Build for all architectures:
+
+```bash
+wails3 build -platform windows/amd64,windows/arm64
+```
+
+## Distribution Checklist
+
+Before distributing your Windows application:
+
+- [ ] Test installer on clean Windows installation
+- [ ] Verify application icon displays correctly
+- [ ] Test uninstaller completely removes application
+- [ ] Verify Start Menu shortcuts work
+- [ ] Test custom protocol handlers (if used)
+- [ ] Check SmartScreen behavior (sign your app if possible)
+- [ ] Test on Windows 10 and Windows 11
+- [ ] Verify file associations work (if used)
+- [ ] Test with antivirus software
+- [ ] Include license and documentation
+
+## Troubleshooting
+
+### NSIS Build Fails
+
+**Error:** `makensis: command not found`
+
+**Solution:**
+```bash
+# Install NSIS
+winget install NSIS.NSIS
+
+# Or download from https://nsis.sourceforge.io/
+```
+
+### Custom Protocols Not Working
+
+**Check registration:**
+```powershell
+# Check registry
+Get-ItemProperty -Path "HKCU:\SOFTWARE\Classes\myapp"
+
+# Test protocol
+Start-Process "myapp://test"
+```
+
+**Fix:**
+1. Reinstall with NSIS installer
+2. Run installer as administrator if needed
+3. Verify `Protocols` configuration in Go code
+
+### SmartScreen Warning
+
+**Cause:** Unsigned executable
+
+**Solutions:**
+1. **Code sign your application** (recommended)
+2. Build reputation (downloads over time)
+3. Submit to Microsoft for analysis
+4. Use Extended Validation (EV) certificate for immediate trust
+
+### Installer Won't Run
+
+**Possible causes:**
+- Antivirus blocking
+- Missing dependencies
+- Corrupted download
+- User permissions
+
+**Solutions:**
+1. Temporarily disable antivirus for testing
+2. Run as administrator
+3. Re-download installer
+4. Check Windows Event Viewer for errors
+
+## Best Practices
+
+### ✅ Do
+
+- **Code sign your releases** - Avoids SmartScreen warnings
+- **Test on clean Windows installations** - Don't rely on dev environment
+- **Provide both installer and portable versions** - Give users choice
+- **Include comprehensive uninstaller** - Remove all traces
+- **Use semantic versioning** - Clear version numbering
+- **Test protocol handlers thoroughly** - Validate all URL inputs
+- **Provide clear installation instructions** - Help users succeed
+
+### ❌ Don't
+
+- **Don't skip code signing** - Users will see scary warnings
+- **Don't require admin for normal apps** - Only if truly necessary
+- **Don't install to non-standard locations** - Use `$PROGRAMFILES64`
+- **Don't leave orphaned registry entries** - Clean up properly
+- **Don't forget to test uninstaller** - Broken uninstallers frustrate users
+- **Don't hardcode paths** - Use Windows environment variables
+
+## Next Steps
+
+- [Code Signing](/guides/build/signing) - Sign your executables and installers
+- [MSIX Packaging](/guides/build/msix) - Modern Windows app packages
+- [Custom Protocols](/guides/distribution/custom-protocols) - Deep linking and URL schemes
+- [Auto-Updates](/guides/distribution/auto-updates) - Keep your app current
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [Windows packaging examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
diff --git a/docs/src/content/docs/guides/building.mdx b/docs/src/content/docs/guides/building.mdx
new file mode 100644
index 000000000..cf6e9ef97
--- /dev/null
+++ b/docs/src/content/docs/guides/building.mdx
@@ -0,0 +1,176 @@
+---
+title: Building Applications
+description: Build and package your Wails application
+sidebar:
+ order: 1
+---
+
+import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
+
+## Overview
+
+Wails provides simple commands to build your application for development and production.
+
+## Development Build
+
+### Quick Start
+
+```bash
+wails3 dev
+```
+
+**Features:**
+- Hot reload for frontend changes
+- Automatic Go rebuild on changes
+- Debug mode enabled
+- Fast iteration
+
+### Dev Options
+
+```bash
+# Specify frontend dev server
+wails3 dev -devserver http://localhost:5173
+
+# Skip frontend dev server
+wails3 dev -nofrontend
+
+# Custom build flags
+wails3 dev -tags dev
+```
+
+## Production Build
+
+### Basic Build
+
+```bash
+wails3 build
+```
+
+**Output:** Optimized binary in `build/bin/`
+
+### Build Options
+
+```bash
+# Build for specific platform
+wails3 build -platform windows/amd64
+
+# Custom output directory
+wails3 build -o ./dist/myapp
+
+# Skip frontend build
+wails3 build -nofrontend
+
+# Production optimizations
+wails3 build -ldflags "-s -w"
+```
+
+## Build Configuration
+
+### wails.json
+
+```json
+{
+ "name": "myapp",
+ "frontend": {
+ "dir": "./frontend",
+ "install": "npm install",
+ "build": "npm run build",
+ "dev": "npm run dev",
+ "devServerUrl": "http://localhost:5173"
+ },
+ "build": {
+ "output": "myapp",
+ "ldflags": "-s -w"
+ }
+}
+```
+
+## Platform-Specific Builds
+
+
+
+ ```bash
+ # Windows executable
+ wails3 build -platform windows/amd64
+
+ # With icon
+ wails3 build -platform windows/amd64 -icon icon.ico
+
+ # Console app (shows terminal)
+ wails3 build -platform windows/amd64 -windowsconsole
+ ```
+
+
+
+ ```bash
+ # macOS app bundle
+ wails3 build -platform darwin/amd64
+
+ # Universal binary (Intel + Apple Silicon)
+ wails3 build -platform darwin/universal
+
+ # With icon
+ wails3 build -platform darwin/amd64 -icon icon.icns
+ ```
+
+
+
+ ```bash
+ # Linux executable
+ wails3 build -platform linux/amd64
+
+ # With icon
+ wails3 build -platform linux/amd64 -icon icon.png
+ ```
+
+
+
+## Optimization
+
+### Binary Size
+
+```bash
+# Strip debug symbols
+wails3 build -ldflags "-s -w"
+
+# UPX compression (external tool)
+upx --best --lzma build/bin/myapp
+```
+
+### Performance
+
+```bash
+# Enable optimizations
+wails3 build -tags production
+
+# Disable debug features
+wails3 build -ldflags "-X main.debug=false"
+```
+
+## Troubleshooting
+
+### Build Fails
+
+**Problem:** Build errors
+
+**Solutions:**
+- Check `go.mod` is up to date
+- Run `go mod tidy`
+- Verify frontend builds: `cd frontend && npm run build`
+- Check wails.json configuration
+
+### Large Binary Size
+
+**Problem:** Binary is too large
+
+**Solutions:**
+- Use `-ldflags "-s -w"` to strip symbols
+- Remove unused dependencies
+- Use UPX compression
+- Check embedded assets size
+
+## Next Steps
+
+- [Cross-Platform Building](/guides/cross-platform) - Build for multiple platforms
+- [Distribution](/guides/installers) - Create installers and packages
+- [Build System](/concepts/build-system) - Understand the build system
diff --git a/docs/src/content/docs/guides/cross-platform.mdx b/docs/src/content/docs/guides/cross-platform.mdx
new file mode 100644
index 000000000..190a2a11f
--- /dev/null
+++ b/docs/src/content/docs/guides/cross-platform.mdx
@@ -0,0 +1,202 @@
+---
+title: Cross-Platform Building
+description: Build for multiple platforms from a single machine
+sidebar:
+ order: 2
+---
+
+## Overview
+
+Wails supports cross-platform compilation, allowing you to build for Windows, macOS, and Linux from any platform.
+
+## Supported Platforms
+
+```bash
+# List available platforms
+wails3 build -platform list
+
+# Common platforms:
+# - windows/amd64
+# - darwin/amd64
+# - darwin/arm64
+# - darwin/universal
+# - linux/amd64
+# - linux/arm64
+```
+
+## Building for Windows
+
+### From macOS/Linux
+
+```bash
+# Install dependencies (one-time)
+# macOS: brew install mingw-w64
+# Linux: apt-get install mingw-w64
+
+# Build for Windows
+wails3 build -platform windows/amd64
+```
+
+### Windows-Specific Options
+
+```bash
+# With custom icon
+wails3 build -platform windows/amd64 -icon app.ico
+
+# Console application
+wails3 build -platform windows/amd64 -windowsconsole
+
+# With manifest
+wails3 build -platform windows/amd64 -manifest app.manifest
+```
+
+## Building for macOS
+
+### From Windows/Linux
+
+```bash
+# Note: macOS builds from other platforms have limitations
+# Recommended: Use macOS for macOS builds
+
+# Build for Intel Macs
+wails3 build -platform darwin/amd64
+
+# Build for Apple Silicon
+wails3 build -platform darwin/arm64
+
+# Universal binary (both architectures)
+wails3 build -platform darwin/universal
+```
+
+### macOS-Specific Options
+
+```bash
+# With custom icon
+wails3 build -platform darwin/universal -icon app.icns
+
+# Code signing (macOS only)
+wails3 build -platform darwin/universal -codesign "Developer ID"
+```
+
+## Building for Linux
+
+### From Any Platform
+
+```bash
+# Build for Linux AMD64
+wails3 build -platform linux/amd64
+
+# Build for Linux ARM64
+wails3 build -platform linux/arm64
+```
+
+### Linux-Specific Options
+
+```bash
+# With custom icon
+wails3 build -platform linux/amd64 -icon app.png
+```
+
+## Build Matrix
+
+### Build All Platforms
+
+```bash
+#!/bin/bash
+# build-all.sh
+
+platforms=("windows/amd64" "darwin/universal" "linux/amd64")
+
+for platform in "${platforms[@]}"; do
+ echo "Building for $platform..."
+ wails3 build -platform "$platform" -o "dist/myapp-$platform"
+done
+```
+
+### Using Taskfile
+
+```yaml
+# Taskfile.yml
+version: '3'
+
+tasks:
+ build:all:
+ desc: Build for all platforms
+ cmds:
+ - task: build:windows
+ - task: build:macos
+ - task: build:linux
+
+ build:windows:
+ desc: Build for Windows
+ cmds:
+ - wails3 build -platform windows/amd64 -o dist/myapp-windows.exe
+
+ build:macos:
+ desc: Build for macOS
+ cmds:
+ - wails3 build -platform darwin/universal -o dist/myapp-macos
+
+ build:linux:
+ desc: Build for Linux
+ cmds:
+ - wails3 build -platform linux/amd64 -o dist/myapp-linux
+```
+
+## CI/CD Integration
+
+### GitHub Actions
+
+```yaml
+name: Build
+
+on: [push]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-go@v4
+ with:
+ go-version: '1.21'
+
+ - name: Install Wails
+ run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
+
+ - name: Build
+ run: wails3 build
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: app-${{ matrix.os }}
+ path: build/bin/
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Test on target platforms
+- Use CI/CD for builds
+- Version your builds
+- Include platform in filename
+- Document build requirements
+
+### ❌ Don't
+
+- Don't skip testing on target platform
+- Don't forget platform-specific icons
+- Don't hardcode paths
+- Don't ignore build warnings
+
+## Next Steps
+
+- [Creating Installers](/guides/installers) - Package your application
+- [Building](/guides/building) - Basic building guide
diff --git a/docs/src/content/docs/guides/distribution/custom-protocols.mdx b/docs/src/content/docs/guides/distribution/custom-protocols.mdx
new file mode 100644
index 000000000..97c4b1372
--- /dev/null
+++ b/docs/src/content/docs/guides/distribution/custom-protocols.mdx
@@ -0,0 +1,593 @@
+---
+title: Custom URL Protocols
+description: Register custom URL schemes to launch your application from links
+sidebar:
+ order: 3
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+Custom URL protocols (also called URL schemes) allow your application to be launched when users click links with your custom protocol, such as `myapp://action` or `myapp://open/document`.
+
+## Overview
+
+Custom protocols enable:
+- **Deep linking**: Launch your app with specific data
+- **Browser integration**: Handle links from web pages
+- **Email links**: Open your app from email clients
+- **Inter-app communication**: Launch from other applications
+
+**Example**: `myapp://open/document?id=123` launches your app and opens document 123.
+
+## Configuration
+
+Define custom protocols in your application options:
+
+```go
+package main
+
+import "github.com/wailsapp/wails/v3/pkg/application"
+
+func main() {
+ app := application.New(application.Options{
+ Name: "My Application",
+ Description: "My awesome application",
+ Protocols: []application.Protocol{
+ {
+ Scheme: "myapp",
+ Description: "My Application Protocol",
+ Role: "Editor", // macOS only
+ },
+ },
+ })
+
+ // Register handler for protocol events
+ app.OnEvent(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) {
+ url := event.Context().ClickedURL()
+ handleCustomURL(url)
+ })
+
+ app.Run()
+}
+
+func handleCustomURL(url string) {
+ // Parse and handle the custom URL
+ // Example: myapp://open/document?id=123
+ println("Received URL:", url)
+}
+```
+
+## Protocol Handler
+
+Listen for protocol events to handle incoming URLs:
+
+```go
+app.OnEvent(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) {
+ url := event.Context().ClickedURL()
+
+ // Parse the URL
+ parsedURL, err := parseCustomURL(url)
+ if err != nil {
+ app.Logger.Error("Failed to parse URL:", err)
+ return
+ }
+
+ // Handle different actions
+ switch parsedURL.Action {
+ case "open":
+ openDocument(parsedURL.DocumentID)
+ case "settings":
+ showSettings()
+ case "user":
+ showUser Profile(parsedURL.UserID)
+ default:
+ app.Logger.Warn("Unknown action:", parsedURL.Action)
+ }
+})
+```
+
+## URL Structure
+
+Design clear, hierarchical URL structures:
+
+```
+myapp://action/resource?param=value
+
+Examples:
+myapp://open/document?id=123
+myapp://settings/theme?mode=dark
+myapp://user/profile?username=john
+```
+
+**Best practices:**
+- Use lowercase scheme names
+- Keep schemes short and memorable
+- Use hierarchical paths for resources
+- Include query parameters for optional data
+- URL-encode special characters
+
+## Platform Registration
+
+Custom protocols are registered differently on each platform.
+
+
+
+
+### Windows NSIS Installer
+
+**Wails v3 automatically registers custom protocols** when using NSIS installers.
+
+#### Automatic Registration
+
+When you build your application with `wails3 build`, the NSIS installer:
+1. Automatically registers all protocols defined in `application.Options.Protocols`
+2. Associates protocols with your application executable
+3. Sets up proper registry entries
+4. Removes protocol associations during uninstall
+
+**No additional configuration required!**
+
+#### How It Works
+
+The NSIS template includes built-in macros:
+- `wails.associateCustomProtocols` - Registers protocols during installation
+- `wails.unassociateCustomProtocols` - Removes protocols during uninstall
+
+These macros are automatically called based on your `Protocols` configuration.
+
+#### Manual Registry (Advanced)
+
+If you need manual registration (outside NSIS):
+
+```batch
+@echo off
+REM Register custom protocol
+REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /ve /d "URL:My Application Protocol" /f
+REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /v "URL Protocol" /t REG_SZ /d "" /f
+REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp\shell\open\command" /ve /d "\"%1\"" /f
+```
+
+#### Testing
+
+Test your protocol registration:
+
+```powershell
+# Open protocol URL from PowerShell
+Start-Process "myapp://test/action"
+
+# Or from command prompt
+start myapp://test/action
+```
+
+
+
+
+
+### Info.plist Configuration
+
+On macOS, protocols are registered via your `Info.plist` file.
+
+#### Automatic Configuration
+
+Wails automatically generates the `Info.plist` with your protocols when you build with `wails3 build`.
+
+The protocols from `application.Options.Protocols` are added to:
+
+```xml
+CFBundleURLTypes
+
+
+ CFBundleURLName
+ My Application Protocol
+ CFBundleURLSchemes
+
+ myapp
+
+ CFBundleTypeRole
+ Editor
+
+
+```
+
+#### Testing
+
+```bash
+# Open protocol URL from terminal
+open "myapp://test/action"
+
+# Check registered handlers
+/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep myapp
+```
+
+
+
+
+
+### Desktop Entry
+
+On Linux, protocols are registered via `.desktop` files.
+
+#### Automatic Configuration
+
+Wails generates a desktop entry file with protocol handlers when you build with `wails3 build`.
+
+**Fixed in v3**: Linux desktop template now properly includes protocol handling.
+
+The generated desktop file includes:
+
+```ini
+[Desktop Entry]
+Type=Application
+Name=My Application
+Exec=/usr/bin/myapp %u
+MimeType=x-scheme-handler/myapp;
+```
+
+#### Manual Registration
+
+If needed, manually install the desktop file:
+
+```bash
+# Copy desktop file
+cp myapp.desktop ~/.local/share/applications/
+
+# Update desktop database
+update-desktop-database ~/.local/share/applications/
+
+# Register protocol handler
+xdg-mime default myapp.desktop x-scheme-handler/myapp
+```
+
+#### Testing
+
+```bash
+# Open protocol URL
+xdg-open "myapp://test/action"
+
+# Check registered handler
+xdg-mime query default x-scheme-handler/myapp
+```
+
+
+
+
+## Complete Example
+
+Here's a complete example handling multiple protocol actions:
+
+```go
+package main
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type App struct {
+ app *application.Application
+ window *application.WebviewWindow
+}
+
+func main() {
+ app := application.New(application.Options{
+ Name: "DeepLink Demo",
+ Description: "Custom protocol demonstration",
+ Protocols: []application.Protocol{
+ {
+ Scheme: "deeplink",
+ Description: "DeepLink Demo Protocol",
+ Role: "Editor",
+ },
+ },
+ })
+
+ myApp := &App{app: app}
+ myApp.setup()
+
+ app.Run()
+}
+
+func (a *App) setup() {
+ // Create window
+ a.window = a.app.NewWebviewWindow(application.WebviewWindowOptions{
+ Title: "DeepLink Demo",
+ Width: 800,
+ Height: 600,
+ URL: "http://wails.localhost/",
+ })
+
+ // Handle custom protocol URLs
+ a.app.OnEvent(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) {
+ customURL := event.Context().ClickedURL()
+ a.handleDeepLink(customURL)
+ })
+}
+
+func (a *App) handleDeepLink(rawURL string) {
+ // Parse URL
+ parsedURL, err := url.Parse(rawURL)
+ if err != nil {
+ a.app.Logger.Error("Failed to parse URL:", err)
+ return
+ }
+
+ // Bring window to front
+ a.window.Show()
+ a.window.SetFocus()
+
+ // Extract path and query
+ path := strings.Trim(parsedURL.Path, "/")
+ query := parsedURL.Query()
+
+ // Handle different actions
+ parts := strings.Split(path, "/")
+ if len(parts) == 0 {
+ return
+ }
+
+ action := parts[0]
+
+ switch action {
+ case "open":
+ if len(parts) >= 2 {
+ resource := parts[1]
+ id := query.Get("id")
+ a.openResource(resource, id)
+ }
+
+ case "settings":
+ section := ""
+ if len(parts) >= 2 {
+ section = parts[1]
+ }
+ a.openSettings(section)
+
+ case "user":
+ if len(parts) >= 2 {
+ username := parts[1]
+ a.openUserProfile(username)
+ }
+
+ default:
+ a.app.Logger.Warn("Unknown action:", action)
+ }
+}
+
+func (a *App) openResource(resourceType, id string) {
+ fmt.Printf("Opening %s with ID: %s\n", resourceType, id)
+ // Emit event to frontend
+ a.app.Event.Emit("navigate", map[string]string{
+ "type": resourceType,
+ "id": id,
+ })
+}
+
+func (a *App) openSettings(section string) {
+ fmt.Printf("Opening settings section: %s\n", section)
+ a.app.Event.Emit("navigate", map[string]string{
+ "page": "settings",
+ "section": section,
+ })
+}
+
+func (a *App) openUserProfile(username string) {
+ fmt.Printf("Opening user profile: %s\n", username)
+ a.app.Event.Emit("navigate", map[string]string{
+ "page": "user",
+ "user": username,
+ })
+}
+```
+
+## Frontend Integration
+
+Handle navigation events in your frontend:
+
+```javascript
+import { Events } from '@wailsio/runtime'
+
+// Listen for navigation events from protocol handler
+Events.On('navigate', (event) => {
+ const { type, id, page, section, user } = event.data
+
+ if (type === 'document') {
+ // Open document with ID
+ router.push(`/document/${id}`)
+ } else if (page === 'settings') {
+ // Open settings
+ router.push(`/settings/${section}`)
+ } else if (page === 'user') {
+ // Open user profile
+ router.push(`/user/${user}`)
+ }
+})
+```
+
+## Security Considerations
+
+### Validate All Input
+
+Always validate and sanitize URLs from external sources:
+
+```go
+func (a *App) handleDeepLink(rawURL string) {
+ // Parse URL
+ parsedURL, err := url.Parse(rawURL)
+ if err != nil {
+ a.app.Logger.Error("Invalid URL:", err)
+ return
+ }
+
+ // Validate scheme
+ if parsedURL.Scheme != "myapp" {
+ a.app.Logger.Warn("Invalid scheme:", parsedURL.Scheme)
+ return
+ }
+
+ // Validate path
+ path := strings.Trim(parsedURL.Path, "/")
+ if !isValidPath(path) {
+ a.app.Logger.Warn("Invalid path:", path)
+ return
+ }
+
+ // Sanitize parameters
+ params := sanitizeQueryParams(parsedURL.Query())
+
+ // Process validated URL
+ a.processDeepLink(path, params)
+}
+
+func isValidPath(path string) bool {
+ // Only allow alphanumeric and forward slashes
+ validPath := regexp.MustCompile(`^[a-zA-Z0-9/]+$`)
+ return validPath.MatchString(path)
+}
+
+func sanitizeQueryParams(query url.Values) map[string]string {
+ sanitized := make(map[string]string)
+ for key, values := range query {
+ if len(values) > 0 {
+ // Take first value and sanitize
+ sanitized[key] = sanitizeString(values[0])
+ }
+ }
+ return sanitized
+}
+```
+
+### Prevent Injection Attacks
+
+Never execute URLs directly as code or SQL:
+
+```go
+// ❌ DON'T: Execute URL content
+func badHandler(url string) {
+ exec.Command("sh", "-c", url).Run() // DANGEROUS!
+}
+
+// ✅ DO: Parse and validate
+func goodHandler(url string) {
+ parsed, _ := url.Parse(url)
+ action := parsed.Query().Get("action")
+
+ // Whitelist allowed actions
+ allowed := map[string]bool{
+ "open": true,
+ "settings": true,
+ "help": true,
+ }
+
+ if allowed[action] {
+ handleAction(action)
+ }
+}
+```
+
+## Testing
+
+### Manual Testing
+
+Test protocol handlers during development:
+
+**Windows:**
+```powershell
+Start-Process "myapp://test/action?id=123"
+```
+
+**macOS:**
+```bash
+open "myapp://test/action?id=123"
+```
+
+**Linux:**
+```bash
+xdg-open "myapp://test/action?id=123"
+```
+
+### HTML Testing
+
+Create a test HTML page:
+
+```html
+
+
+
+ Protocol Test
+
+
+ Custom Protocol Test Links
+
+
+
+
+```
+
+## Troubleshooting
+
+### Protocol Not Registered
+
+**Windows:**
+- Check registry: `HKEY_CURRENT_USER\SOFTWARE\Classes\`
+- Reinstall with NSIS installer
+- Verify installer ran with proper permissions
+
+**macOS:**
+- Rebuild application with `wails3 build`
+- Check `Info.plist` in app bundle: `MyApp.app/Contents/Info.plist`
+- Reset Launch Services: `/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill`
+
+**Linux:**
+- Check desktop file: `~/.local/share/applications/myapp.desktop`
+- Update database: `update-desktop-database ~/.local/share/applications/`
+- Verify handler: `xdg-mime query default x-scheme-handler/myapp`
+
+### Application Not Launching
+
+**Check logs:**
+```go
+app := application.New(application.Options{
+ Logger: application.NewLogger(application.LogLevelDebug),
+ // ...
+})
+```
+
+**Common issues:**
+- Application not installed in expected location
+- Executable path in registration doesn't match actual location
+- Permissions issues
+
+## Best Practices
+
+### ✅ Do
+
+- **Use descriptive scheme names** - `mycompany-myapp` instead of `mca`
+- **Validate all input** - Never trust URLs from external sources
+- **Handle errors gracefully** - Log invalid URLs, don't crash
+- **Provide user feedback** - Show what action was triggered
+- **Test on all platforms** - Protocol handling varies
+- **Document your URL structure** - Help users and integrators
+
+### ❌ Don't
+
+- **Don't use common scheme names** - Avoid `http`, `file`, `app`, etc.
+- **Don't execute URLs as code** - Huge security risk
+- **Don't expose sensitive operations** - Require confirmation for destructive actions
+- **Don't assume protocols work everywhere** - Have fallback mechanisms
+- **Don't forget URL encoding** - Handle special characters properly
+
+## Next Steps
+
+- [Windows Packaging](/guides/build/windows) - Learn about NSIS installer options
+- [File Associations](/guides/distribution/file-associations) - Open files with your app
+- [Single Instance](/guides/distribution/single-instance) - Prevent multiple app instances
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
diff --git a/docs/src/content/docs/guides/e2e-testing.mdx b/docs/src/content/docs/guides/e2e-testing.mdx
new file mode 100644
index 000000000..f461afa2e
--- /dev/null
+++ b/docs/src/content/docs/guides/e2e-testing.mdx
@@ -0,0 +1,173 @@
+---
+title: End-to-End Testing
+description: Test complete user workflows
+sidebar:
+ order: 6
+---
+
+## Overview
+
+End-to-end testing validates complete user workflows in your application.
+
+## Using Playwright
+
+### Setup
+
+```bash
+# Install Playwright
+npm install -D @playwright/test
+
+# Initialize
+npx playwright install
+```
+
+### Configuration
+
+```javascript
+// playwright.config.js
+import { defineConfig } from '@playwright/test'
+
+export default defineConfig({
+ testDir: './e2e',
+ use: {
+ baseURL: 'http://localhost:34115', // Wails dev server
+ },
+})
+```
+
+### Writing Tests
+
+```javascript
+// e2e/app.spec.js
+import { test, expect } from '@playwright/test'
+
+test('create note', async ({ page }) => {
+ await page.goto('/')
+
+ // Click new note button
+ await page.click('#new-note-btn')
+
+ // Fill in title
+ await page.fill('#note-title', 'Test Note')
+
+ // Fill in content
+ await page.fill('#note-content', 'Test content')
+
+ // Verify note appears in list
+ await expect(page.locator('.note-item')).toContainText('Test Note')
+})
+
+test('delete note', async ({ page }) => {
+ await page.goto('/')
+
+ // Create a note first
+ await page.click('#new-note-btn')
+ await page.fill('#note-title', 'To Delete')
+
+ // Delete it
+ await page.click('#delete-btn')
+
+ // Confirm dialog
+ page.on('dialog', dialog => dialog.accept())
+
+ // Verify it's gone
+ await expect(page.locator('.note-item')).not.toContainText('To Delete')
+})
+```
+
+## Testing dialogs
+
+```javascript
+test('file save dialog', async ({ page }) => {
+ await page.goto('/')
+
+ // Intercept file dialog
+ page.on('filechooser', async (fileChooser) => {
+ await fileChooser.setFiles('/path/to/test/file.json')
+ })
+
+ // Trigger save
+ await page.click('#save-btn')
+
+ // Verify success message
+ await expect(page.locator('.success-message')).toBeVisible()
+})
+```
+
+## Testing Window Behaviour
+
+```javascript
+test('window state', async ({ page }) => {
+ await page.goto('/')
+
+ // Test window title
+ await expect(page).toHaveTitle('My App')
+
+ // Test window size
+ const size = await page.viewportSize()
+ expect(size.width).toBe(800)
+ expect(size.height).toBe(600)
+})
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Test critical user flows
+- Use data-testid attributes
+- Clean up test data
+- Run tests in CI/CD
+- Test error scenarios
+- Keep tests independent
+
+### ❌ Don't
+
+- Don't test implementation details
+- Don't write brittle selectors
+- Don't skip cleanup
+- Don't ignore flaky tests
+- Don't test everything
+
+## Running E2E Tests
+
+```bash
+# Run all tests
+npx playwright test
+
+# Run in headed mode
+npx playwright test --headed
+
+# Run specific test
+npx playwright test e2e/app.spec.js
+
+# Debug mode
+npx playwright test --debug
+```
+
+## CI/CD Integration
+
+```yaml
+# .github/workflows/e2e.yml
+name: E2E Tests
+
+on: [push]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ - name: Install dependencies
+ run: npm ci
+ - name: Install Playwright
+ run: npx playwright install --with-deps
+ - name: Run tests
+ run: npx playwright test
+```
+
+## Next Steps
+
+- [Testing](/guides/testing) - Learn unit testing
+- [Building](/guides/building) - Build your application
diff --git a/docs/src/content/docs/guides/events-reference.mdx b/docs/src/content/docs/guides/events-reference.mdx
index 569534495..b18cddc2d 100644
--- a/docs/src/content/docs/guides/events-reference.mdx
+++ b/docs/src/content/docs/guides/events-reference.mdx
@@ -74,7 +74,7 @@ import (
func (s *Service) UpdateData() {
// Do some data processing...
-
+
// Notify the frontend
app := application.Get()
app.Event.Emit("my-app:data-updated",
@@ -100,7 +100,7 @@ Events.Emit('myapp:close-window')
Events.Emit('myapp:disconnect-requested', 'id-123')
```
-If you are using typescript in your frontend and [application.RegisterEvent](/learn/events#custom-event-registration) in your Go code, you will get event name autocomplete / checking and data type checking.
+If you are using TypeScript in your frontend and [registering typed events](#typed-events-with-typecheck) in your Go code, you will get event name autocomplete/checking and data type checking.
### Removing Event Listeners
@@ -155,7 +155,7 @@ import { Events } from '@wailsio/runtime';
Events.On('common:ThemeChanged', (event) => {
const isDarkMode = event.data.isDark;
-
+
if (isDarkMode) {
document.body.classList.add('dark-theme');
document.body.classList.remove('light-theme');
@@ -175,7 +175,7 @@ import { Events } from '@wailsio/runtime';
Events.On('common:WindowFilesDropped', (event) => {
const files = event.data.files;
-
+
files.forEach(file => {
console.log('File dropped:', file);
// Process the dropped files
@@ -194,7 +194,7 @@ import { Events } from '@wailsio/runtime';
Events.On('common:WindowClosing', () => {
// Save user data before closing
saveApplicationState();
-
+
// You could also prevent closing by returning false
// from a registered window close handler
});
@@ -237,7 +237,7 @@ Events.On('mac:ApplicationWillTerminate', () => {
## Creating Custom Events
-You can create your own events for application-specific needs. See [application.RegisterEvent](/learn/events#custom-event-registration) to learn how to register these events and enable type checking of event data.
+You can create your own events for application-specific needs.
### Backend (Go)
@@ -256,7 +256,6 @@ func (s *Service) ProcessUserData(userData UserData) error {
"timestamp": time.Now(),
},
)
-
return nil
}
@@ -281,7 +280,7 @@ import { Events } from '@wailsio/runtime';
// Listen for your custom events
Events.On('user:data-processed', (event) => {
const { userId, status, timestamp } = event.data;
-
+
showNotification(`User ${userId} processing ${status}`);
updateUIWithNewData();
});
@@ -291,6 +290,128 @@ Events.On('monitor:stats-updated', (event) => {
});
```
+## Typed Events with Type Safety
+
+Wails v3 supports typed events with full TypeScript type safety through event registration and automatic binding generation.
+
+### Registering Custom Events
+
+Call `application.RegisterEvent` at init time to register custom event names with their data types:
+
+```go
+package main
+
+import "github.com/wailsapp/wails/v3/pkg/application"
+
+type UserLoginData struct {
+ UserID string
+ Username string
+ LoginTime string
+}
+
+type MonitorStats struct {
+ CPUUsage float64
+ MemoryUsage float64
+}
+
+func init() {
+ // Register events with their data types
+ application.RegisterEvent[UserLoginData]("user:login")
+ application.RegisterEvent[MonitorStats]("monitor:stats")
+
+ // Register events without data (void events)
+ application.RegisterEvent[application.Void]("app:ready")
+}
+```
+
+:::caution
+`RegisterEvent` is meant to be called at init time and will panic if:
+- Arguments are not valid
+- The same event name is registered twice with different data types
+:::
+
+:::note
+It is safe to register the same event multiple times as long as the data type is always the same. This can be useful to ensure an event is registered when any of multiple packages is loaded.
+:::
+
+### Benefits of Event Registration
+
+Once registered, data arguments passed to `Event.Emit` are type-checked against the specified type. On mismatch:
+- An error is emitted and logged (or passed to the registered error handler)
+- The offending event will not be propagated
+- This ensures the data field of registered events is always assignable to the declared type
+
+### Strict Mode
+
+Use the `strictevents` build tag to enable warnings for unregistered events in development:
+
+```bash
+go build -tags strictevents
+```
+
+With strict mode enabled, the runtime emits at most one warning per unregistered event name to avoid spamming logs.
+
+### TypeScript Binding Generation
+
+The binding generator outputs TypeScript definitions and glue code for transparent typed event support in the frontend.
+
+#### 1. Set up the Vite Plugin
+
+In your `vite.config.ts`:
+
+```typescript
+import { defineConfig } from 'vite'
+import wails from '@wailsio/runtime/plugins/vite'
+
+export default defineConfig({
+ plugins: [wails()],
+})
+```
+
+#### 2. Generate Bindings
+
+Run the binding generator:
+
+```bash
+wails3 generate bindings
+```
+
+This creates TypeScript files in your frontend directory with typed event creators and data interfaces.
+
+#### 3. Use Typed Events in Frontend
+
+```typescript
+import { Events } from '@wailsio/runtime'
+import { UserLogin, MonitorStats } from './bindings/events'
+
+// Type-safe event emission with autocomplete
+Events.Emit(UserLogin({
+ UserID: "123",
+ Username: "john_doe",
+ LoginTime: new Date().toISOString()
+}))
+
+// Type-safe event listening
+Events.On(UserLogin, (event) => {
+ // event.data is typed as UserLoginData
+ console.log(`User ${event.data.Username} logged in`)
+})
+
+Events.On(MonitorStats, (event) => {
+ // event.data is typed as MonitorStats
+ updateDashboard({
+ cpu: event.data.CPUUsage,
+ memory: event.data.MemoryUsage
+ })
+})
+```
+
+The typed events provide:
+- **Autocomplete** for event names
+- **Type checking** for event data
+- **Compile-time errors** for mismatched data types
+- **IntelliSense** documentation
+
## Event Reference
### Common Events (Cross-platform)
@@ -354,6 +475,8 @@ Core Linux window events:
When creating custom events, use namespaces to avoid conflicts:
```javascript
+import { Events } from '@wailsio/runtime';
+
// Good - namespaced events
Events.Emit('myapp:user:login');
Events.Emit('myapp:data:updated');
@@ -376,9 +499,9 @@ useEffect(() => {
const handler = (event) => {
// Handle event
};
-
+
Events.On('common:WindowResize', handler);
-
+
// Cleanup
return () => {
Events.Off('common:WindowResize', handler);
diff --git a/docs/src/content/docs/guides/gin-routing.mdx b/docs/src/content/docs/guides/gin-routing.mdx
index c4704a4bb..fc3ce289a 100644
--- a/docs/src/content/docs/guides/gin-routing.mdx
+++ b/docs/src/content/docs/guides/gin-routing.mdx
@@ -9,9 +9,9 @@ This guide demonstrates how to integrate the [Gin web framework](https://github.
Wails v3 provides a flexible asset system that allows you to use any HTTP handler, including popular web frameworks like Gin. This integration enables you to:
-- Serve web content using Gin's powerful routing and middleware capabilities
-- Create RESTful APIs that can be accessed from your Wails application
-- Leverage Gin's extensive feature set whilst maintaining the benefits of Wails
+- Serve web content using Gin's routing and middleware
+- Create RESTful APIs accessible from your Wails application
+- Use Gin's features while maintaining Wails desktop integration
## Setting Up Gin with Wails
@@ -102,43 +102,20 @@ app.Window.NewWithOptions(application.WebviewWindowOptions{
## Serving Static Content
-There are several ways to serve static content with Gin in a Wails application:
-
-### Option 1: Using Go's embed Package
-
-The recommended approach is to use Go's `embed` package to embed static files into your binary:
+Use Go's `embed` package to embed static files into your binary:
```go
//go:embed static
var staticFiles embed.FS
-// In your main function:
ginEngine.StaticFS("/static", http.FS(staticFiles))
-
-// Serve index.html
ginEngine.GET("/", func(c *gin.Context) {
- file, err := staticFiles.ReadFile("static/index.html")
- if err != nil {
- c.String(http.StatusInternalServerError, "Error reading index.html")
- return
- }
+ file, _ := staticFiles.ReadFile("static/index.html")
c.Data(http.StatusOK, "text/html; charset=utf-8", file)
})
```
-### Option 2: Serving from Disk
-
-For development purposes, you might prefer to serve files directly from disk:
-
-```go
-// Serve static files from the "static" directory
-ginEngine.Static("/static", "./static")
-
-// Serve index.html
-ginEngine.GET("/", func(c *gin.Context) {
- c.File("./static/index.html")
-})
-```
+For development, serve files directly from disk using `ginEngine.Static("/static", "./static")`.
## Custom Middleware
@@ -206,219 +183,42 @@ ginEngine.GET("/api/search", func(c *gin.Context) {
})
```
-## Event Communication
+## Using Wails Features
-One of the powerful features of Wails is its event system, which allows for communication between the frontend and backend. When using Gin as a service, you can still leverage this event system by serving the Wails runtime.js file to your frontend.
+Your Gin-served web content can interact with Wails features using the `@wailsio/runtime` package.
-### Serving the Wails Runtime
+### Event Handling
-To enable event communication, you need to serve the Wails runtime.js file at the `/wails/runtime.js` path. Here's how to implement this in your Gin service:
+Register event handlers in Go:
```go
-import (
- "io"
- "net/http"
-
- "github.com/gin-gonic/gin"
- "github.com/wailsapp/wails/v3/pkg/application"
- "github.com/wailsapp/wails/v3/pkg/runtime"
-)
-
-// GinService implements a Wails service that uses Gin for HTTP handling
-type GinService struct {
- ginEngine *gin.Engine
- app *application.App
- // Other fields...
-}
-
-// ServeHTTP implements the http.Handler interface
-func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // Special handling for Wails runtime.js
- if r.URL.Path == "/wails/runtime.js" {
- s.serveWailsRuntime(w, r)
- return
- }
-
- // All other requests go to the Gin router
- s.ginEngine.ServeHTTP(w, r)
-}
-
-// serveWailsRuntime serves the Wails runtime.js file
-func (s *GinService) serveWailsRuntime(w http.ResponseWriter, r *http.Request) {
- // Open the runtime.js file from the public runtime package
- runtimeFile, err := runtime.Assets.Open(runtime.RuntimeJSPath)
- if err != nil {
- http.Error(w, "Failed to access runtime assets", http.StatusInternalServerError)
- return
- }
- defer runtimeFile.Close()
-
- // Set the content type
- w.Header().Set("Content-Type", "application/javascript")
-
- // Copy the file to the response
- _, err = io.Copy(w, runtimeFile)
- if err != nil {
- http.Error(w, "Failed to serve runtime.js", http.StatusInternalServerError)
- }
-}
-```
-
-### Handling Events
-
-You'll also need to add an endpoint to handle events from the frontend. This endpoint will bridge the gap between the HTTP requests and the Wails event system:
-
-```go
-// In your setupRoutes method
-func (s *GinService) setupRoutes() {
- // Event handling endpoint
- s.ginEngine.POST("/events/emit", func(c *gin.Context) {
- var eventData struct {
- Name string `json:"name" binding:"required"`
- Data interface{} `json:"data"`
- }
-
- if err := c.ShouldBindJSON(&eventData); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- // Process the event using the Wails event system
- s.app.Event.Emit(eventData.Name, eventData.Data)
-
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "Event processed successfully",
- })
- })
-
- // Other routes...
-}
-```
-
-### Using Events in the Frontend
-
-In your frontend HTML, include the Wails runtime.js script and use the event API:
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-This approach allows you to use the Wails event system seamlessly with your Gin service, providing a consistent experience across your application.
-
-## Interacting with Wails
-
-Your Gin-served web content can interact with Wails features like events and bindings. To enable this interaction, you
-must use the JavaScript API package `@wailsio/runtime`.
-
-### Handling Wails Events in Go
-
-```go
-// Register event handler
app.Event.On("my-event", func(event *application.CustomEvent) {
- log.Printf("Received event from frontend: %v", event.Data)
- // Process the event...
+ log.Printf("Received event: %v", event.Data)
})
```
-### Emitting Events from JavaScript
+Call from JavaScript using the runtime:
```html
-
+
```
## Advanced Configuration
### Customising Gin's Mode
-Gin has three modes: debug, release, and test. For production applications, you should use release mode:
+Set Gin to release mode for production:
```go
-// Set Gin to release mode
-gin.SetMode(gin.ReleaseMode)
-
-// Create a new Gin router
+gin.SetMode(gin.ReleaseMode) // Use gin.DebugMode for development
ginEngine := gin.New()
```
-You can use Go's build tags to set the mode based on the build environment:
-
-```go[main_prod.go]
-// +build production
-
-var ginMode = gin.ReleaseMode
-```
-
-```go[main_dev.go]
-// +build !production
-
-var ginMode = gin.DebugMode
-```
-
-```go [main.go]
-// In your main function:
-gin.SetMode(ginMode)
-```
-
### Handling WebSockets
You can integrate WebSockets with Gin using libraries like Gorilla WebSocket:
@@ -447,126 +247,6 @@ ginEngine.GET("/ws", func(c *gin.Context) {
})
```
-## Complete Example
-
-Here's a complete example of integrating Gin with Wails:
-
-```go
-package main
-
-import (
- "embed"
- "log"
- "net/http"
- "time"
-
- "github.com/gin-gonic/gin"
- "github.com/wailsapp/wails/v3/pkg/application"
-)
-
-//go:embed static
-var staticFiles embed.FS
-
-// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails
-func GinMiddleware(ginEngine *gin.Engine) application.Middleware {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Let Wails handle the `/wails` route
- if r.URL.Path == "/wails" {
- next.ServeHTTP(w, r)
- return
- }
- // Let Gin handle everything else
- ginEngine.ServeHTTP(w, r)
- })
- }
-}
-
-// LoggingMiddleware is a Gin middleware that logs request details
-func LoggingMiddleware() gin.HandlerFunc {
- return func(c *gin.Context) {
- // Start timer
- startTime := time.Now()
-
- // Process request
- c.Next()
-
- // Calculate latency
- latency := time.Since(startTime)
-
- // Log request details
- log.Printf("[GIN] %s | %s | %s | %d | %s",
- c.Request.Method,
- c.Request.URL.Path,
- c.ClientIP(),
- c.Writer.Status(),
- latency,
- )
- }
-}
-
-func main() {
- // Create a new Gin router
- ginEngine := gin.New() // Using New() instead of Default() to add our own middleware
-
- // Add middlewares
- ginEngine.Use(gin.Recovery())
- ginEngine.Use(LoggingMiddleware())
-
- // Serve embedded static files
- ginEngine.StaticFS("/static", http.FS(staticFiles))
-
- // Define routes
- ginEngine.GET("/", func(c *gin.Context) {
- file, err := staticFiles.ReadFile("static/index.html")
- if err != nil {
- c.String(http.StatusInternalServerError, "Error reading index.html")
- return
- }
- c.Data(http.StatusOK, "text/html; charset=utf-8", file)
- })
-
- ginEngine.GET("/api/hello", func(c *gin.Context) {
- c.JSON(http.StatusOK, gin.H{
- "message": "Hello from Gin API!",
- "time": time.Now().Format(time.RFC3339),
- })
- })
-
- // Create a new Wails application
- app := application.New(application.Options{
- Name: "Gin Example",
- Description: "A demo of using Gin with Wails",
- Mac: application.MacOptions{
- ApplicationShouldTerminateAfterLastWindowClosed: true,
- },
- Assets: application.AssetOptions{
- Handler: ginEngine,
- Middleware: GinMiddleware(ginEngine),
- },
- })
-
- // Register event handler
- app.Event.On("gin-button-clicked", func(event *application.CustomEvent) {
- log.Printf("Received event from frontend: %v", event.Data)
- })
-
- // Create window
- app.Window.NewWithOptions(application.WebviewWindowOptions{
- Title: "Wails + Gin Example",
- Width: 900,
- Height: 700,
- URL: "/",
- })
-
- // Run the app
- err := app.Run()
- if err != nil {
- log.Fatal(err)
- }
-}
-```
-
## Best Practices
- **Use Go's embed Package:** Embed static files into your binary for better distribution.
@@ -578,6 +258,6 @@ func main() {
## Conclusion
-Integrating Gin with Wails provides a powerful combination for building desktop applications with web technologies. Gin's performance and feature set complement Wails' desktop integration capabilities, allowing you to create sophisticated applications that leverage the best of both worlds.
+Integrating Gin with Wails provides a powerful combination for building desktop applications with web technologies. Gin's performance and feature set complement Wails' desktop integration capabilities, allowing you to create sophisticated applications that use the best of both worlds.
For more information, refer to the [Gin documentation](https://github.com/gin-gonic/gin) and the [Wails documentation](https://wails.io).
diff --git a/docs/src/content/docs/guides/installers.mdx b/docs/src/content/docs/guides/installers.mdx
new file mode 100644
index 000000000..170b26865
--- /dev/null
+++ b/docs/src/content/docs/guides/installers.mdx
@@ -0,0 +1,160 @@
+---
+title: Creating Installers
+description: Package your application for distribution
+sidebar:
+ order: 3
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+
+## Overview
+
+Create professional installers for your Wails application on all platforms.
+
+## Platform Installers
+
+
+
+ ### NSIS Installer
+
+ ```bash
+ # Install NSIS
+ # Download from: https://nsis.sourceforge.io/
+
+ # Create installer script (installer.nsi)
+ makensis installer.nsi
+ ```
+
+ **installer.nsi:**
+ ```nsis
+ !define APPNAME "MyApp"
+ !define VERSION "1.0.0"
+
+ Name "${APPNAME}"
+ OutFile "MyApp-Setup.exe"
+ InstallDir "$PROGRAMFILES\${APPNAME}"
+
+ Section "Install"
+ SetOutPath "$INSTDIR"
+ File "build\bin\myapp.exe"
+ CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\myapp.exe"
+ SectionEnd
+ ```
+
+ ### WiX Toolset
+
+ Alternative for MSI installers.
+
+
+
+ ### DMG Creation
+
+ ```bash
+ # Create DMG
+ hdiutil create -volname "MyApp" -srcfolder build/bin/MyApp.app -ov -format UDZO MyApp.dmg
+ ```
+
+ ### Code Signing
+
+ ```bash
+ # Sign application
+ codesign --deep --force --verify --verbose --sign "Developer ID" MyApp.app
+
+ # Notarize
+ xcrun notarytool submit MyApp.dmg --apple-id "email" --password "app-password"
+ ```
+
+ ### App Store
+
+ Use Xcode for App Store distribution.
+
+
+
+ ### DEB Package
+
+ ```bash
+ # Create package structure
+ mkdir -p myapp_1.0.0/DEBIAN
+ mkdir -p myapp_1.0.0/usr/bin
+
+ # Copy binary
+ cp build/bin/myapp myapp_1.0.0/usr/bin/
+
+ # Create control file
+ cat > myapp_1.0.0/DEBIAN/control << EOF
+ Package: myapp
+ Version: 1.0.0
+ Architecture: amd64
+ Maintainer: Your Name
+ Description: My Application
+ EOF
+
+ # Build package
+ dpkg-deb --build myapp_1.0.0
+ ```
+
+ ### RPM Package
+
+ Use `rpmbuild` for RPM-based distributions.
+
+ ### AppImage
+
+ ```bash
+ # Use appimagetool
+ appimagetool myapp.AppDir
+ ```
+
+
+
+## Automated Packaging
+
+### Using GoReleaser
+
+```yaml
+# .goreleaser.yml
+project_name: myapp
+
+builds:
+ - binary: myapp
+ goos:
+ - windows
+ - darwin
+ - linux
+ goarch:
+ - amd64
+ - arm64
+
+archives:
+ - format: zip
+ name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
+
+nfpms:
+ - formats:
+ - deb
+ - rpm
+ vendor: Your Company
+ homepage: https://example.com
+ description: My Application
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Code sign on all platforms
+- Include version information
+- Create uninstallers
+- Test installation process
+- Provide clear documentation
+
+### ❌ Don't
+
+- Don't skip code signing
+- Don't forget file associations
+- Don't hardcode paths
+- Don't skip testing
+
+## Next Steps
+
+- [Auto-Updates](/guides/auto-updates) - Implement automatic updates
+- [Cross-Platform Building](/guides/cross-platform) - Build for multiple platforms
diff --git a/docs/src/content/docs/guides/performance.mdx b/docs/src/content/docs/guides/performance.mdx
new file mode 100644
index 000000000..9b4f28064
--- /dev/null
+++ b/docs/src/content/docs/guides/performance.mdx
@@ -0,0 +1,341 @@
+---
+title: Performance Optimisation
+description: Optimise your Wails application for maximum performance
+sidebar:
+ order: 9
+---
+
+## Overview
+
+Optimise your Wails application for speed, memory efficiency, and responsiveness.
+
+## Frontend Optimisation
+
+### Bundle Size
+
+```javascript
+// vite.config.js
+export default {
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ vendor: ['react', 'react-dom'],
+ },
+ },
+ },
+ minify: 'terser',
+ terserOptions: {
+ compress: {
+ drop_console: true,
+ },
+ },
+ },
+}
+```
+
+### Code Splitting
+
+```javascript
+// Lazy load components
+const Settings = lazy(() => import('./Settings'))
+
+function App() {
+ return (
+ }>
+
+
+ )
+}
+```
+
+### Asset Optimisation
+
+```javascript
+// Optimise images
+import { defineConfig } from 'vite'
+import imagemin from 'vite-plugin-imagemin'
+
+export default defineConfig({
+ plugins: [
+ imagemin({
+ gifsicle: { optimizationLevel: 3 },
+ optipng: { optimizationLevel: 7 },
+ svgo: { plugins: [{ removeViewBox: false }] },
+ }),
+ ],
+})
+```
+
+## Backend Optimisation
+
+### Efficient Bindings
+
+```go
+// ❌ Bad: Return everything
+func (s *Service) GetAllData() []Data {
+ return s.db.FindAll() // Could be huge
+}
+
+// ✅ Good: Paginate
+func (s *Service) GetData(page, size int) (*PagedData, error) {
+ return s.db.FindPaged(page, size)
+}
+```
+
+### Caching
+
+```go
+type CachedService struct {
+ cache *lru.Cache
+ ttl time.Duration
+}
+
+func (s *CachedService) GetData(key string) (interface{}, error) {
+ // Check cache
+ if val, ok := s.cache.Get(key); ok {
+ return val, nil
+ }
+
+ // Fetch and cache
+ data, err := s.fetchData(key)
+ if err != nil {
+ return nil, err
+ }
+
+ s.cache.Add(key, data)
+ return data, nil
+}
+```
+
+### Goroutines for Long Operations
+
+```go
+func (s *Service) ProcessLargeFile(path string) error {
+ // Process in background
+ go func() {
+ result, err := s.process(path)
+ if err != nil {
+ s.app.EmitEvent("process-error", err.Error())
+ return
+ }
+ s.app.EmitEvent("process-complete", result)
+ }()
+
+ return nil
+}
+```
+
+## Memory Optimisation
+
+### Avoid Memory Leaks
+
+```go
+// ❌ Bad: Goroutine leak
+func (s *Service) StartPolling() {
+ ticker := time.NewTicker(1 * time.Second)
+ go func() {
+ for range ticker.C {
+ s.poll()
+ }
+ }()
+ // ticker never stopped!
+}
+
+// ✅ Good: Proper cleanup
+func (s *Service) StartPolling() {
+ ticker := time.NewTicker(1 * time.Second)
+ s.stopChan = make(chan bool)
+
+ go func() {
+ for {
+ select {
+ case <-ticker.C:
+ s.poll()
+ case <-s.stopChan:
+ ticker.Stop()
+ return
+ }
+ }
+ }()
+}
+
+func (s *Service) StopPolling() {
+ close(s.stopChan)
+}
+```
+
+### Pool Resources
+
+```go
+var bufferPool = sync.Pool{
+ New: func() interface{} {
+ return new(bytes.Buffer)
+ },
+}
+
+func processData(data []byte) []byte {
+ buf := bufferPool.Get().(*bytes.Buffer)
+ defer bufferPool.Put(buf)
+
+ buf.Reset()
+ buf.Write(data)
+ // Process...
+ return buf.Bytes()
+}
+```
+
+## Event Optimisation
+
+### Debounce Events
+
+```javascript
+// Debounce frequent events
+let debounceTimer
+function handleInput(value) {
+ clearTimeout(debounceTimer)
+ debounceTimer = setTimeout(() => {
+ UpdateData(value)
+ }, 300)
+}
+```
+
+### Batch Updates
+
+```go
+type BatchProcessor struct {
+ items []Item
+ mu sync.Mutex
+ timer *time.Timer
+}
+
+func (b *BatchProcessor) Add(item Item) {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ b.items = append(b.items, item)
+
+ if b.timer == nil {
+ b.timer = time.AfterFunc(100*time.Millisecond, b.flush)
+ }
+}
+
+func (b *BatchProcessor) flush() {
+ b.mu.Lock()
+ items := b.items
+ b.items = nil
+ b.timer = nil
+ b.mu.Unlock()
+
+ // Process batch
+ processBatch(items)
+}
+```
+
+## Build Optimisation
+
+### Binary Size
+
+```bash
+# Strip debug symbols
+wails3 build -ldflags "-s -w"
+
+# Reduce binary size further
+go build -ldflags="-s -w" -trimpath
+```
+
+### Compilation Speed
+
+```bash
+# Use build cache
+go build -buildmode=default
+
+# Parallel compilation
+go build -p 8
+```
+
+## Profiling
+
+### CPU Profiling
+
+```go
+import "runtime/pprof"
+
+func profileCPU() {
+ f, _ := os.Create("cpu.prof")
+ defer f.Close()
+
+ pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+
+ // Code to profile
+}
+```
+
+### Memory Profiling
+
+```go
+import "runtime/pprof"
+
+func profileMemory() {
+ f, _ := os.Create("mem.prof")
+ defer f.Close()
+
+ runtime.GC()
+ pprof.WriteHeapProfile(f)
+}
+```
+
+### Analyse Profiles
+
+```bash
+# View CPU profile
+go tool pprof cpu.prof
+
+# View memory profile
+go tool pprof mem.prof
+
+# Web interface
+go tool pprof -http=:8080 cpu.prof
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Profile before optimising
+- Cache expensive operations
+- Use pagination for large datasets
+- Debounce frequent events
+- Pool resources
+- Clean up goroutines
+- Optimise bundle size
+- Use lazy loading
+
+### ❌ Don't
+
+- Don't optimise prematurely
+- Don't ignore memory leaks
+- Don't block the main thread
+- Don't return huge datasets
+- Don't skip profiling
+- Don't forget cleanup
+
+## Performance Checklist
+
+- [ ] Frontend bundle optimised
+- [ ] Images compressed
+- [ ] Code splitting implemented
+- [ ] Backend methods paginated
+- [ ] Caching implemented
+- [ ] Goroutines cleaned up
+- [ ] Events debounced
+- [ ] Binary size optimised
+- [ ] Profiling done
+- [ ] Memory leaks fixed
+
+## Next Steps
+
+- [Architecture](/guides/architecture) - Application architecture patterns
+- [Testing](/guides/testing) - Test your application
+- [Building](/guides/building) - Build optimised binaries
diff --git a/docs/src/content/docs/guides/security.mdx b/docs/src/content/docs/guides/security.mdx
new file mode 100644
index 000000000..51bc21b3e
--- /dev/null
+++ b/docs/src/content/docs/guides/security.mdx
@@ -0,0 +1,261 @@
+---
+title: Security Best Practices
+description: Secure your Wails application
+sidebar:
+ order: 8
+---
+
+## Overview
+
+Security is critical for desktop applications. Follow these practices to keep your application secure.
+
+## Input Validation
+
+### Always Validate
+
+```go
+func (s *UserService) CreateUser(email, password string) (*User, error) {
+ // Validate email
+ if !isValidEmail(email) {
+ return nil, errors.New("invalid email")
+ }
+
+ // Validate password strength
+ if len(password) < 8 {
+ return nil, errors.New("password too short")
+ }
+
+ // Sanitise input
+ email = strings.TrimSpace(email)
+ email = html.EscapeString(email)
+
+ // Continue...
+}
+```
+
+### Sanitise HTML
+
+```go
+import "html"
+
+func (s *Service) SaveComment(text string) error {
+ // Escape HTML
+ text = html.EscapeString(text)
+
+ // Validate length
+ if len(text) > 1000 {
+ return errors.New("comment too long")
+ }
+
+ return s.db.Save(text)
+}
+```
+
+## Authentication
+
+### Secure Password Storage
+
+```go
+import "golang.org/x/crypto/bcrypt"
+
+func hashPassword(password string) (string, error) {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ return string(hash), err
+}
+
+func verifyPassword(hash, password string) bool {
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
+ return err == nil
+}
+```
+
+### Session Management
+
+```go
+type Session struct {
+ UserID int
+ Token string
+ ExpiresAt time.Time
+}
+
+func (a *AuthService) CreateSession(userID int) (*Session, error) {
+ token := generateSecureToken()
+
+ session := &Session{
+ UserID: userID,
+ Token: token,
+ ExpiresAt: time.Now().Add(24 * time.Hour),
+ }
+
+ return session, a.saveSession(session)
+}
+```
+
+## Data Protection
+
+### Encrypt Sensitive Data
+
+```go
+import "crypto/aes"
+import "crypto/cipher"
+
+func encrypt(data []byte, key []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+
+ nonce := make([]byte, gcm.NonceSize())
+ return gcm.Seal(nonce, nonce, data, nil), nil
+}
+```
+
+### Secure Storage
+
+```go
+// Use OS keychain for sensitive data
+import "github.com/zalando/go-keyring"
+
+func saveAPIKey(key string) error {
+ return keyring.Set("myapp", "api_key", key)
+}
+
+func getAPIKey() (string, error) {
+ return keyring.Get("myapp", "api_key")
+}
+```
+
+## Network Security
+
+### Use HTTPS
+
+```go
+func makeAPICall(url string) (*Response, error) {
+ // Always use HTTPS
+ if !strings.HasPrefix(url, "https://") {
+ return nil, errors.New("only HTTPS allowed")
+ }
+
+ return http.Get(url)
+}
+```
+
+### Verify Certificates
+
+```go
+import "crypto/tls"
+
+func secureClient() *http.Client {
+ return &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ MinVersion: tls.VersionTLS12,
+ },
+ },
+ }
+}
+```
+
+## File Operations
+
+### Validate Paths
+
+```go
+func readFile(path string) ([]byte, error) {
+ // Prevent path traversal
+ if strings.Contains(path, "..") {
+ return nil, errors.New("invalid path")
+ }
+
+ // Check file exists in allowed directory
+ absPath, err := filepath.Abs(path)
+ if err != nil {
+ return nil, err
+ }
+
+ if !strings.HasPrefix(absPath, allowedDir) {
+ return nil, errors.New("access denied")
+ }
+
+ return os.ReadFile(absPath)
+}
+```
+
+## Rate Limiting
+
+```go
+type RateLimiter struct {
+ requests map[string][]time.Time
+ mu sync.Mutex
+ limit int
+ window time.Duration
+}
+
+func (r *RateLimiter) Allow(key string) bool {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ now := time.Now()
+
+ // Clean old requests
+ var recent []time.Time
+ for _, t := range r.requests[key] {
+ if now.Sub(t) < r.window {
+ recent = append(recent, t)
+ }
+ }
+
+ if len(recent) >= r.limit {
+ return false
+ }
+
+ r.requests[key] = append(recent, now)
+ return true
+}
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Validate all input
+- Use HTTPS for network calls
+- Encrypt sensitive data
+- Use secure password hashing
+- Implement rate limiting
+- Keep dependencies updated
+- Log security events
+- Use OS keychains
+
+### ❌ Don't
+
+- Don't trust user input
+- Don't store passwords in plain text
+- Don't hardcode secrets
+- Don't skip certificate verification
+- Don't expose sensitive data in logs
+- Don't use weak encryption
+- Don't ignore security updates
+
+## Security Checklist
+
+- [ ] All user input validated
+- [ ] Passwords hashed with bcrypt
+- [ ] Sensitive data encrypted
+- [ ] HTTPS used for all network calls
+- [ ] Rate limiting implemented
+- [ ] File paths validated
+- [ ] Dependencies up to date
+- [ ] Security logging enabled
+- [ ] Error messages don't leak info
+- [ ] Code reviewed for vulnerabilities
+
+## Next Steps
+
+- [Architecture](/guides/architecture) - Application architecture patterns
+- [Best Practices](/features/bindings/best-practices) - Bindings best practices
diff --git a/docs/src/content/docs/guides/testing.mdx b/docs/src/content/docs/guides/testing.mdx
new file mode 100644
index 000000000..4bd3a07b8
--- /dev/null
+++ b/docs/src/content/docs/guides/testing.mdx
@@ -0,0 +1,175 @@
+---
+title: Testing
+description: Test your Wails application
+sidebar:
+ order: 5
+---
+
+## Overview
+
+Testing ensures your application works correctly and prevents regressions.
+
+## Unit Testing
+
+### Testing Services
+
+```go
+func TestUserService_Create(t *testing.T) {
+ service := &UserService{
+ users: make(map[string]*User),
+ }
+
+ user, err := service.Create("john@example.com", "password123")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if user.Email != "john@example.com" {
+ t.Errorf("expected email john@example.com, got %s", user.Email)
+ }
+}
+```
+
+### Testing with Mocks
+
+```go
+type MockDB struct {
+ users map[string]*User
+}
+
+func (m *MockDB) Create(user *User) error {
+ m.users[user.ID] = user
+ return nil
+}
+
+func TestUserService_WithMock(t *testing.T) {
+ mockDB := &MockDB{users: make(map[string]*User)}
+ service := &UserService{db: mockDB}
+
+ user, err := service.Create("test@example.com", "pass")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(mockDB.users) != 1 {
+ t.Error("expected 1 user in mock")
+ }
+}
+```
+
+## Integration Testing
+
+### Testing with Real Dependencies
+
+```go
+func TestIntegration(t *testing.T) {
+ // Setup test database
+ db, err := sql.Open("sqlite3", ":memory:")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+
+ // Create schema
+ _, err = db.Exec(`CREATE TABLE users (...)`)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Test service
+ service := &UserService{db: db}
+ user, err := service.Create("test@example.com", "password")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify in database
+ var count int
+ db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
+ if count != 1 {
+ t.Errorf("expected 1 user, got %d", count)
+ }
+}
+```
+
+## Frontend Testing
+
+### JavaScript Unit Tests
+
+```javascript
+// Using Vitest
+import { describe, it, expect } from 'vitest'
+import { formatDate } from './utils'
+
+describe('formatDate', () => {
+ it('formats date correctly', () => {
+ const date = new Date('2024-01-01')
+ expect(formatDate(date)).toBe('2024-01-01')
+ })
+})
+```
+
+### Testing Bindings
+
+```javascript
+import { vi } from 'vitest'
+import { GetUser } from './bindings/myapp/userservice'
+
+// Mock the binding
+vi.mock('./bindings/myapp/userservice', () => ({
+ GetUser: vi.fn()
+}))
+
+describe('User Component', () => {
+ it('loads user data', async () => {
+ GetUser.mockResolvedValue({ name: 'John', email: 'john@example.com' })
+
+ // Test your component
+ const user = await GetUser(1)
+ expect(user.name).toBe('John')
+ })
+})
+```
+
+## Best Practices
+
+### ✅ Do
+
+- Write tests before fixing bugs
+- Test edge cases
+- Use table-driven tests
+- Mock external dependencies
+- Test error handling
+- Keep tests fast
+
+### ❌ Don't
+
+- Don't skip error cases
+- Don't test implementation details
+- Don't write flaky tests
+- Don't ignore test failures
+- Don't skip integration tests
+
+## Running Tests
+
+```bash
+# Run Go tests
+go test ./...
+
+# Run with coverage
+go test -cover ./...
+
+# Run specific test
+go test -run TestUserService
+
+# Run frontend tests
+cd frontend && npm test
+
+# Run with watch mode
+cd frontend && npm test -- --watch
+```
+
+## Next Steps
+
+- [End-to-End Testing](/guides/e2e-testing) - Test complete user flows
+- [Best Practices](/features/bindings/best-practices) - Learn best practices
diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx
index 3b71e86be..eb1f6693e 100644
--- a/docs/src/content/docs/index.mdx
+++ b/docs/src/content/docs/index.mdx
@@ -1,114 +1,294 @@
---
-title: Welcome to Wails
-description: "Create beautiful applications using Go"
+title: Build Desktop Apps with Go
+description: "Native desktop applications using Go and web technologies"
banner:
content: |
- This site is for v3 Alpha and is under construction. v2 is the stable release.
+ Wails v3 is in ALPHA. v2 docs
template: splash
hero:
- tagline:
- A powerful framework for building desktop applications using Go and modern web
- technologies.
+ tagline: Build beautiful, performant desktop applications using Go and modern web technologies. One codebase. Three platforms. No browsers.
image:
dark: ../../assets/wails-logo-dark.svg
light: ../../assets/wails-logo-light.svg
alt: Wails Logo
-
actions:
- - text: Getting Started
- link: /getting-started/installation
+ - text: Get Started
+ link: /quick-start/installation
icon: right-arrow
- - text: Sponsor
- link: https://github.com/sponsors/leaanthony
- icon: heart
- class:
- - is-sponsor
+ variant: primary
+ - text: View Tutorial
+ link: /tutorials/03-notes-vanilla
+ icon: open-book
+ variant: secondary
---
-import { Card, CardGrid } from "@astrojs/starlight/components";
-import CardAnimation from '../../components/CardAnimation.astro';
+import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
- :::danger[Alpha Status]
- Wails v3 is currently in ALPHA. Please follow our [feedback guide](/feedback) to help us improve the project.
- This site is still under construction.
- :::
+
-
+## Quickstart
-
-
-
- Build desktop applications that combine the power of Go with modern web technologies:
+
+
+```bash
+# Install Wails
+go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- - **Native Performance**: Direct in-memory Go-to-Frontend communication
- - **Cross Platform**: One codebase for Windows, macOS and Linux
- - **Modern Stack**: Use any modern web framework (React, Vue, Svelte, etc.)
- - **Native APIs**: Access OS-level features directly from Go
- - **Developer Experience**: Hot reload, native dialogs, system tray, and more
- - **Small Footprint**: Lightweight alternative to Electron
-
+# Create your application
+wails3 init -n myapp -t vanilla
-
- Before you begin, ensure you have:
+# Run with hot reload
+cd myapp && wails3 dev
+```
+
+
+
+```powershell
+# Install Wails
+go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- - Go 1.23 or later installed
- - Latest version of npm installed (if you want to use the templates)
- - Basic knowledge of Go programming
+# Create your application
+wails3 init -n myapp -t vanilla
- Check our [detailed prerequisites](/getting-started/installation#dependencies) for a complete list.
-
+# Run with hot reload
+cd myapp; wails3 dev
+```
+
+
+
+```bash
+# Install Wails
+go install github.com/wailsapp/wails/v3/cmd/wails3@latest
-
- ```bash
- # Install wails
- go install github.com/wailsapp/wails/v3/cmd/wails3@latest
+# Create your application
+wails3 init -n myapp -t vanilla
- # Create a new project
- wails3 init -n myproject
+# Run with hot reload
+cd myapp && wails3 dev
+```
+
+
- # Run your project
- cd myproject
- wails3 dev
- ```
-
+**Your application is now running** with hot reload and type-safe Go-to-JS bindings.
-
- Wails v3 is currently in Alpha. While it's stable enough for testing and
- development, there might be breaking changes before the final release. Your feedback
- and contributions are welcome!
-
-
- - **Multiple Windows Support**: Create and manage multiple windows in a single application
- - **Improved API Design**: New procedural approach for more flexibility
- - **Enhanced Bindings**: Sophisticated static analyzer for Go-to-Frontend communication
- - **Better Build Tooling**: A new build system based on [Taskfile](https://taskfile.dev/)
- - **New Linux Packaging**: Support for deb, rpm, arch linux, and AppImage
- - **New Templates**: Create applications with a single command using our pre-built templates
-
+## Why Wails?
-
- Ready to build your first Wails application? Check out our
- [installation guide](/getting-started/installation) to get started.
-
+
+
+ - ~15MB binaries vs Electron's 150MB
+ - ~10MB baseline memory vs 100MB+
+ - <0.5s startup time vs 2-3s
+ - Native rendering using OS WebView
+ - No bundled browser overhead
+
+
+
+ - One Go codebase for all platforms
+ - Any web framework - React, Vue, Svelte
+ - Hot reload during development
+ - Auto-generated bindings to easily call Go from Javascript
+ - In-memory IPC. No network ports
+
+
+
+ - Multiple windows with lifecycles
+ - Native menus and system tray
+ - Platform-native file dialogs
+ - System integration and shortcuts
+ - Code signing and packaging tools
+
+
+
+ - Single codebase for Windows, macOS, Linux
+ - Platform-specific features when needed
+ - No compromise on user experience
+ - Deploy to all platforms from one build
+ - Mobile coming soon...
+
+
-
- We value your feedback and have a [detailed guide on providing it](/feedback).
- Use this guide to:
- - Report bugs
- - Request features
- - Get help from the community
- - Contribute to the project
- Join our [Discord community](https://discord.gg/bdj28QNHmT)
- or visit our [GitHub repository](https://github.com/wailsapp/wails) to:
-
-
- Please note that v3 is currently in Alpha. While we're working hard to ensure
- stability, there might be breaking changes before the final release. For production
- use, please use [Wails v2](https://wails.io).
-
-
-
+## Next Steps
+
+Next: [Build a complete application](/tutorials/03-notes-vanilla), browse [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples), or check the [API reference](/reference/application). Migrating from v2? See the [upgrade guide](/migration/v2-to-v3).
+
+
+:::note[Production Ready]
+Wails v3 is in **ALPHA**. The API is *reasonably* stable, and applications are running in production. We're refining documentation and tooling before the final release.
+:::
+
+
+ Wails is free and open source, built by developers for developers. If Wails helps you build amazing applications, consider supporting its continued development.
+
+ Your sponsorship helps maintain the project, improve documentation, and develop new features that benefit the entire community.
+
+ [Become a Sponsor →](https://github.com/sponsors/leaanthony)
+
+
diff --git a/docs/src/content/docs/learn/application-menu.mdx b/docs/src/content/docs/learn/application-menu.mdx
deleted file mode 100644
index 5613d09ff..000000000
--- a/docs/src/content/docs/learn/application-menu.mdx
+++ /dev/null
@@ -1,262 +0,0 @@
----
-title: Application Menu
-sidebar:
- order: 53
----
-
-import { Tabs, TabItem } from "@astrojs/starlight/components";
-
-Application menus provide the main menu bar interface for your application. They appear at the top of the window on Windows and Linux, and at the top of the screen on macOS.
-
-## Creating an Application Menu
-
-Create a new application menu using the `New` method from the Menu manager:
-
-```go
-menu := app.Menu.New()
-```
-
-## Setting the Menu
-
-The way to set the menu varies on the platform:
-
-
-
-
- On macOS, there is only one menu bar per application. Set the menu using the `Set` method of the Menu manager:
-
- ```go
- app.Menu.Set(menu)
- ```
-
-
-
-
-
- On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window:
-
- ```go
- app.Window.Current().SetMenu(menu)
- ```
-
-
-
-
-
- On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window:
-
- ```go
- app.Window.Current().SetMenu(menu)
- ```
-
-
-
-
-
-## Menu Roles
-
-Wails provides predefined menu roles that automatically create platform-appropriate menu structures:
-
-```go
-// Add standard application menu on macOS
-if runtime.GOOS == "darwin" {
- menu.AddRole(application.AppMenu)
-}
-
-// Add standard menus
-menu.AddRole(application.FileMenu)
-menu.AddRole(application.EditMenu)
-menu.AddRole(application.WindowMenu)
-menu.AddRole(application.HelpMenu)
-```
-
-:::note[Platform Behaviour]
-The AppMenu role is specific to macOS and provides the standard application menu containing About, Preferences, and Quit items.
-:::
-
-### Available Roles
-
-| Role | Description | Platform Notes |
-|------|-------------|----------------|
-| `AppMenu` | Standard application menu | macOS only |
-| `FileMenu` | File operations menu | All platforms |
-| `EditMenu` | Text editing operations | All platforms |
-| `WindowMenu` | Window management | All platforms |
-| `HelpMenu` | Help and information | All platforms |
-
-## Custom Menus
-
-Create custom menus by adding items directly:
-
-```go
-// Add a custom menu
-customMenu := menu.AddSubmenu("Tools")
-customMenu.Add("Settings").OnClick(func(ctx *application.Context) {
- // Show settings dialogue
-})
-```
-
-:::tip[Menu Items]
-For detailed information about available menu item types and properties, refer to the [Menu Reference](./menu-reference) documentation.
-:::
-
-## Window Control
-
-Menu items can control the application windows:
-
-```go
-viewMenu := menu.AddSubmenu("View")
-viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
- window := app.Window.Current()
- if window.Fullscreen() {
- window.SetFullscreen(false)
- } else {
- window.SetFullscreen(true)
- }
-})
-```
-
-## Dynamic Menus
-
-Menus can be updated dynamically based on application state:
-
-```go
-projectMenu := menu.AddSubmenu("Project")
-saveItem := projectMenu.Add("Save Project")
-
-// Update based on state
-saveItem.OnClick(func(ctx *application.Context) {
- if projectSaved {
- saveItem.SetEnabled(false)
- saveItem.SetLabel("Project Saved")
- }
- menu.Update()
-})
-```
-
-## Platform-Specific Considerations
-
-
-
-
- On macOS, menus are deeply integrated with the system:
-
- - Menus appear in the system menu bar at the top of the screen
- - The application menu (⌘) is required and should be added using `menu.AddRole(application.AppMenu)`
- - Standard keyboard shortcuts are automatically handled
- - Menu styling follows system appearance
- - The "About" menu item appears in the application menu
- - Preferences are typically placed in the application menu
-
-
-
-
-
- On Windows, menus follow the traditional Windows UI guidelines:
-
- - Menus appear in the application window's title bar
- - Standard keyboard shortcuts should be explicitly set using `SetAccelerator`
- - Menu styling matches the Windows theme
- - The "About" menu item typically appears in the Help menu
- - Settings/Preferences are typically placed in the Tools menu
-
-
-
-
-
- On Linux, menu behaviour depends on the desktop environment:
-
- - Menu appearance adapts to the desktop environment's theme
- - Some desktop environments (like Unity) support global menu bars
- - Menu placement follows the desktop environment's conventions
- - Keyboard shortcuts should be explicitly set
- - Settings are typically placed in the Edit menu
-
-
-
-
-## Best Practices
-
-1. Use standard menu roles where appropriate
-2. Follow platform-specific menu conventions
-3. Provide keyboard shortcuts for common actions
-4. Keep menu structures shallow and organised
-5. Update menu items to reflect application state
-6. Use clear, concise menu labels
-7. Group related items logically
-
-:::danger[Warning]
-Always test menu functionality across all target platforms to ensure consistent behaviour and appearance.
-:::
-
-:::tip[Pro Tip]
-Consider using the `app.Window.Current()` method in menu handlers to affect the active window, rather than storing window references.
-:::
-
-## Complete Example
-
-Here's a comprehensive example demonstrating various menu features:
-
-```go
-package main
-
-import (
- "runtime"
- "github.com/wailsapp/wails/v3/pkg/application"
-)
-
-func main() {
- app := application.New(application.Options{
- Name: "Menu Demo",
- })
-
- // Create main menu
- menu := app.Menu.New()
-
- // Add platform-specific application menu
- if runtime.GOOS == "darwin" {
- menu.AddRole(application.AppMenu)
- }
-
- // Add standard menus
- fileMenu := menu.AddRole(application.FileMenu)
- menu.AddRole(application.EditMenu)
- menu.AddRole(application.WindowMenu)
- menu.AddRole(application.HelpMenu)
-
- // Add custom menu
- toolsMenu := menu.AddSubmenu("Tools")
-
- // Add checkbox item
- toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) {
- isDark := ctx.ClickedMenuItem().Checked()
- // Toggle theme
- })
-
- // Add radio group
- toolsMenu.AddRadio("Small Text", true).OnClick(handleFontSize)
- toolsMenu.AddRadio("Medium Text", false).OnClick(handleFontSize)
- toolsMenu.AddRadio("Large Text", false).OnClick(handleFontSize)
-
- // Add submenu
- advancedMenu := toolsMenu.AddSubmenu("Advanced")
- advancedMenu.Add("Configure...").OnClick(func(ctx *application.Context) {
- // Show configuration
- })
-
- // Set the menu
- app.Menu.Set(menu)
-
- // Create main window
- app.Window.New()
-
- err := app.Run()
- if err != nil {
- panic(err)
- }
-}
-
-func handleFontSize(ctx *application.Context) {
- size := ctx.ClickedMenuItem().Label()
- // Update font size
-}
diff --git a/docs/src/content/docs/learn/binding-best-practices.mdx b/docs/src/content/docs/learn/binding-best-practices.mdx
deleted file mode 100644
index 4536fc3c9..000000000
--- a/docs/src/content/docs/learn/binding-best-practices.mdx
+++ /dev/null
@@ -1,115 +0,0 @@
----
-title: Binding Best Practices
-sidebar:
- order: 23
----
-
-import { FileTree } from "@astrojs/starlight/components";
-
-This guide provides best practices and patterns for using the Wails binding system effectively in your applications.
-
-## Service Design Patterns
-
-### Service Organization
-
-Organize your services based on functionality rather than technical concerns. For example, instead of having a single large service, split it into smaller, focused services:
-
-```go
-// Instead of this:
-type AppService struct {}
-
-func (s *AppService) GetUser() User { /* ... */ }
-func (s *AppService) UpdateUser(user User) error { /* ... */ }
-func (s *AppService) GetProducts() []Product { /* ... */ }
-func (s *AppService) AddProduct(product Product) error { /* ... */ }
-
-// Do this:
-type UserService struct {}
-func (s *UserService) GetUser() User { /* ... */ }
-func (s *UserService) UpdateUser(user User) error { /* ... */ }
-
-type ProductService struct {}
-func (s *ProductService) GetProducts() []Product { /* ... */ }
-func (s *ProductService) AddProduct(product Product) error { /* ... */ }
-```
-
-This makes your code more maintainable and easier to understand.
-
-### Use JSON Tags
-
-Use JSON tags to control how your models are serialized:
-
-```go
-type User struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Email string `json:"email"`
- Password string `json:"-"` // Exclude from JSON
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-```
-
-### Separate Frontend and Backend Models
-
-Consider using different models for the frontend and backend:
-
-```go
-// Backend model
-type User struct {
- ID int
- Name string
- Email string
- Password string // Sensitive data
- CreatedAt time.Time
- UpdatedAt time.Time
-}
-
-// Frontend model
-type UserDTO struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Email string `json:"email"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-func (s *UserService) GetUser() UserDTO {
- user := getUserFromDatabase()
- return UserDTO{
- ID: user.ID,
- Name: user.Name,
- Email: user.Email,
- CreatedAt: user.CreatedAt,
- }
-}
-```
-
-This gives you more control over what data is exposed to the frontend.
-
-### Use Context for Cancellation
-
-Use context for cancellation to avoid wasting resources on abandoned requests:
-
-```go
-func (s *ProductService) GetProducts(ctx context.Context, req ProductsRequest) (ProductsResponse, error) {
- // Check if the request has been cancelled
- select {
- case <-ctx.Done():
- return ProductsResponse{}, ctx.Err()
- default:
- // Continue processing
- }
-
- products, total, err := getProductsFromDatabase(ctx, req.Page, req.PageSize, req.Filter)
- if err != nil {
- return ProductsResponse{}, err
- }
-
- return ProductsResponse{
- Products: products,
- Total: total,
- Page: req.Page,
- PageSize: req.PageSize,
- }, nil
-}
-```
diff --git a/docs/src/content/docs/learn/bindings.mdx b/docs/src/content/docs/learn/bindings.mdx
deleted file mode 100644
index ef91df413..000000000
--- a/docs/src/content/docs/learn/bindings.mdx
+++ /dev/null
@@ -1,674 +0,0 @@
----
-title: Bindings
-sidebar:
- order: 20
----
-
-import { FileTree } from "@astrojs/starlight/components";
-
-## Introduction
-
-One of the key features of Wails is the ability to seamlessly integrate backend
-Go code with the frontend, enabling efficient communication between the two.
-This can be done manually by sending messages between the frontend and backend,
-but this can be cumbersome and error-prone, especially when dealing with complex
-data types.
-
-The bindings generator in Wails v3 simplifies this process by automatically
-generating JavaScript or TypeScript functions and models that reflect the
-methods and data structures defined in your Go code. This means you can write
-your backend logic in Go and easily expose it to the frontend without the need
-for manual binding or complex integration.
-
-This guide is designed to help you understand and utilize this powerful binding
-tool.
-
-## Core Concepts
-
-In Wails v3, services can be added to your application. These services act as a
-bridge between the backend and frontend, allowing you to define methods and
-state that can be accessed and manipulated from the frontend.
-
-### Services
-
-1. Services can hold state and expose methods that operate on that state.
-2. Services can be used similar to controllers in HTTP web applications or as
- services.
-3. Only public methods on the service are bound, following Go's convention.
-
-Here's a simple example of how you can define a service and add it to your Wails
-application:
-
-```go
-package main
-
-import (
- "log"
- "github.com/wailsapp/wails/v3/pkg/application"
-)
-
-type GreetService struct {}
-
-func (g *GreetService) Greet(name string) string {
- return "Hello " + name
-}
-
-func main() {
- app := application.New(application.Options{
- Services: []application.Service{
- application.NewService(&GreetService{}),
- },
- })
- // ....
- err := app.Run()
- if err != nil {
- log.Fatal(err)
- }
-}
-```
-
-In this example, we define a `GreetService` services with a public `Greet`
-method. The `Greet` method takes a `name` parameter and returns a greeting
-string.
-
-We then create a new Wails application using `application.New` and add the
-`GreetService` service to the application using the `Services` option in the
-`application.Options`. The `application.NewService` method must always be given
-an _instance_ of the service struct, not the service struct type itself.
-
-### Generating the Bindings
-
-By binding the struct, Wails is able to generate the necessary JavaScript or
-TypeScript code by running the following command in the project directory:
-
-```bash
-wails3 generate bindings
-```
-
-The bindings generator will scan the project and dependencies for anything that
-needs generating. Note: It will take longer the very first time you run the
-bindings generator, as it will be building up a cache of packages to scan. You
-should see output similar to the following:
-
-```bash
-% wails3 generate bindings
- INFO 347 Packages, 1 Service, 1 Method, 0 Enums, 0 Models in 1.981036s.
- INFO Output directory: /Users/me/myproject/frontend/bindings
-```
-
-If we look in the `frontend/bindings` directory, we should see the following
-files:
-
-
-
-- frontend/bindings
- - changeme
- - greetservice.js
- - index.js
-
-
-
-NOTE: The `changeme` directory is the name of the module defined in `go.mod` and
-is used to namespace the generated files.
-
-The generated `greetservice.js` file contains the JavaScript code that mirrors
-the Go struct and its methods:
-
-```javascript title="greetservice.js"
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore: Unused imports
-import { Call as $Call, Create as $Create } from "@wailsio/runtime";
-
-/**
- * @param {string} name
- * @returns {Promise & { cancel(): void }}
- */
-export function Greet(name) {
- let $resultPromise = /** @type {any} */ ($Call.ByID(1411160069, name));
- return $resultPromise;
-}
-```
-
-As you can see, it also generates all the necessary JSDoc type information to
-ensure type safety in your frontend code.
-
-### Using the Bindings
-
-You can import and use this file in your frontend code to interact with the
-backend.
-
-```javascript
-import { Greet } from "./bindings/changeme/greetservice.js";
-
-console.log(Greet("Alice")); // Output: Hello Alice
-```
-
-### Binding Models
-
-In addition to binding methods, you can also use structs as input or output
-parameters in your bound methods. When structs are used as parameters, Wails
-generates corresponding JavaScript versions of those types.
-
-Let's extend the previous example to use a `Person` type that has a `Name`
-field:
-
-```go
-package main
-
-import (
- "github.com/wailsapp/wails/v3/pkg/application"
- "log"
-)
-
-// Person defines a person
-type Person struct {
- // Name of the person
- Name string
-}
-
-type GreetService struct{}
-
-func (g *GreetService) Greet(person Person) string {
- return "Hello " + person.Name
-}
-
-func main() {
- app := application.New(application.Options{
- Services: []application.Service{
- application.NewService(&GreetService{}),
- },
- })
- // ....
- err := app.Run()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-```
-
-In this updated example, we define a `Person` struct with a `Name` field. The
-`Greet` method in the `GreetService` service now takes a `Person` as an input
-parameter.
-
-When you run the bindings generator, Wails will generate a corresponding
-JavaScript `Person` type that mirrors the Go struct. This allows you to create
-instances of the `Person` type in your frontend code and pass them to the bound
-`Greet` method.
-
-If we run the bindings generator again, we should see the following output:
-
-```bash
-% wails3 generate bindings
- INFO Processed: 347 Packages, 1 Service, 1 Method, 0 Enums, 1 Model in 1.9943997s.
- INFO Output directory: /Users/me/myproject/frontend/bindings
-```
-
-In the `frontend/bindings/changeme` directory, you should see a new `models.js`
-file containing the following code:
-
-```javascript title="models.js"
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore: Unused imports
-import { Create as $Create } from "@wailsio/runtime";
-
-/**
- * Person defines a person
- */
-export class Person {
- /**
- * Creates a new Person instance.
- * @param {Partial} [$$source = {}] - The source object to create the Person.
- */
- constructor($$source = {}) {
- if (!("Name" in $$source)) {
- /**
- * Name of the person
- * @member
- * @type {string}
- */
- this["Name"] = "";
- }
-
- Object.assign(this, $$source);
- }
-
- /**
- * Creates a new Person instance from a string or object.
- * @param {any} [$$source = {}]
- * @returns {Person}
- */
- static createFrom($$source = {}) {
- let $$parsedSource =
- typeof $$source === "string" ? JSON.parse($$source) : $$source;
- return new Person(/** @type {Partial} */ ($$parsedSource));
- }
-}
-```
-
-The `Person` class is generated with a constructor that takes an optional
-`source` parameter, which allows you to create a new `Person` instance from an
-object. It also has a static `createFrom` method that can create a `Person`
-instance from a string or object.
-
-You may also notice that comments in the Go struct are kept in the generated
-JavaScript code! This can be helpful for understanding the purpose of the fields
-and methods in the generated models and should be picked up by your IDE.
-
-### Using Bound Models
-
-Here's an example of how you can use the generated JavaScript `Person` type in
-your frontend code:
-
-```javascript
-import { Greet } from "./bindings/changeme/greetservice.js";
-import { Person } from "./bindings/changeme/models.js";
-
-const resultElement = document.getElementById("result");
-
-async function doGreet() {
- let person = new Person({ Name: document.getElementById("name").value });
- if (!person.Name) {
- person.Name = "anonymous";
- }
- resultElement.innerText = await Greet(person);
-}
-```
-
-In this example, we import the generated `Person` type from the `models` module.
-We create a new instance of `Person`, set its `Name` property, and pass it to
-the `Greet` method.
-
-Using bound models allows you to work with complex data structures and
-seamlessly pass them between the frontend and backend of your Wails application.
-
-### Index files
-
-The generator outputs an additional `index.js` file that re-exports all services and models:
-
-```javascript title="index.js"
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-import * as GreetService from "./greetservice.js";
-export {
- GreetService
-};
-
-export {
- Person
-} from "./models.js";
-```
-
-You can take advantage of this feature
-to aggregate import statements for multiple services and models.
-If you are building your frontend with a bundler,
-which is the default for most project templates,
-you can also simplify the import path:
-
-```javascript
-import { GreetService, Person } from "./bindings/changeme";
-await GreetService.Greet(new Person(/* ... */));
-```
-
-### Using Typescript
-
-To generate TypeScript bindings instead of JavaScript, you can use the `-ts`
-flag:
-
-```bash
-% wails3 generate bindings -ts
-```
-
-This will generate TypeScript files in the `frontend/bindings` directory:
-
-
-
-- frontend/bindings
- - main
- - greetservice.ts
- - index.ts
- - models.ts
-
-
-
-The generated files include `greetservice.ts`, which contains the TypeScript
-code for the bound struct and its methods, and `models.ts`, which contains the
-TypeScript types for the bound models:
-
-```typescript title="GreetService.ts"
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore: Unused imports
-import { Call as $Call, Create as $Create } from "@wailsio/runtime";
-
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore: Unused imports
-import * as $models from "./models.js";
-
-export function Greet(
- person: $models.Person,
-): Promise & { cancel(): void } {
- let $resultPromise = $Call.ByID(1411160069, person) as any;
- return $resultPromise;
-}
-```
-
-```typescript title="models.ts"
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-/**
- * Person defines a person
- */
-export class Person {
- /**
- * Name of the person
- */
- "Name": string;
-
- /** Creates a new Person instance. */
- constructor(source: Partial = {}) {
- if (!("Name" in source)) {
- this["Name"] = "";
- }
-
- Object.assign(this, source);
- }
-
- /** Creates a new Person instance from a string or object. */
- static createFrom(source: string | object = {}): Person {
- let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
- return new Person(parsedSource as Partial);
- }
-}
-```
-
-Using TypeScript bindings provides type safety and improved IDE support when
-working with the generated code in your frontend.
-
-### Using `context.Context`
-
-When defining service methods in Go, you can include `context.Context` as the
-first parameter. The runtime will automatically provide a context when the
-method is called from the frontend.
-
-The context provides several powerful features:
-
-1. **Cancellation Support**: Long-running operations can be cancelled from the
- frontend, which will raise an error through the Promise chain.
-
-2. **Window Information**: You can determine which window made the call using
- the context key `application.WindowKey`.
-
-Here are some examples:
-
-```go
-// Basic context usage with cancellation
-func (s *MyService) LongRunningTask(ctx context.Context, input string) (string, error) {
- select {
- // Check if the context has been cancelled from the frontend
- case <-ctx.Done():
- return "", ctx.Err()
- default:
- // Process task
- return "completed", nil
- }
-}
-
-// Getting caller window information
-func (s *MyService) WindowAwareMethod(ctx context.Context) (string, error) {
- window := ctx.Value(application.WindowKey).(application.Window)
- return fmt.Sprintf("Called from window: %s (ID: %s)", window.Name(), window.ID()), nil
-}
-```
-
-From the frontend, these methods can be called normally. If you need to cancel a
-long-running operation, you can call the special `cancel` method on the promise
-and it will reject immediately with a special cancellation error;
-the Go context will be cancelled and the actual result of the call will be discarded:
-
-```javascript
-// Call the method
-const promise = MyService.LongRunningTask("input");
-
-// Cancel it later if needed
-// This will cause the context to be cancelled in the Go method
-promise.cancel();
-```
-
-In fact, the runtime returns a special promise wrapper
-that provides cancellation support for arbitrarily long promise chains.
-For example:
-
-```javascript
-import { CancelError } from "@wailsio/runtime";
-
-// Call the method and process its output
-const promise = MyService.LongRunningTask("input").then((result) => {
- console.log(result);
-}).catch((err) => {
- if (err instanceof CancelError) {
- console.log("Cancelled.", err.cause);
- } else {
- console.error("Failed.", err);
- }
-});
-
-// Later...
-// cancel() accepts an optional cause parameter
-// that will be attached to the cancellation error:
-promise.cancel("I'm tired of waiting!").then(() => {
- // Cancellation has been requested successfully
- // and all handlers attached above have run.
- console.log("Ready for the next adventure!");
-});
-```
-
-The `cancel` method returns a promise that fulfills always (and never rejects)
-after the cancellation request has been submitted successfully
-and all previously attached handlers have run.
-
-:::note
-Calling the `cancel` method on a settled promise is safe and has no effect;
-if the task completes before the call to `cancel`, the code above is going to log:
-
-```
-completed
-Ready for the next adventure!
-```
-
-However, if `cancel` is called before the task finishes, the output will be:
-
-```
-Cancelled. I'm tired of waiting!
-Ready for the next adventure!
-```
-:::
-
-The approach discussed above requires storing and chaining promises manually,
-which can be cumbersome for code written in `async`/`await` style.
-If you target plaforms that support the `AbortController`/`AbortSignal` idiom,
-you can call the `cancelOn` method and tie call cancellation to an `AbortSignal` instead:
-
-```javascript
-async function callBinding(signal) {
- try {
- await MyService.LongRunningTask("input").cancelOn(signal);
- } catch (err) {
- if (err instanceof CancelError) {
- console.log("Cancelled! Cause: ", err.cause);
- } else {
- console.error("Failed! Error: ", err);
- }
- }
-}
-
-let controller = new AbortController();
-callBinding(controller.signal);
-
-// Later...
-controller.abort("I'm tired of waiting!");
-```
-
-:::caution
-On the macOS platform, `AbortSignal` is only supported from macOS 10.15 Catalina onwards.
-:::
-
-### Handling errors
-
-As you may have noticed above, bound methods can return errors, which are handled specially.
-When a result field has type `error`, it is omitted by default from the values returned to JS.
-When such a field is _non-nil_, the promise rejects with a `RuntimeError` exception
-that wraps the Go error message:
-
-```go
-func (*MyService) FailingMethod(name string) error {
- return fmt.Errorf("Welcome to an imperfect world, %s", name)
-}
-```
-
-```js
-import { MyService } from './bindings/changeme';
-
-try {
- await MyService.FailingMethod("CLU")
-} catch (err) {
- if (err.name === 'RuntimeError') {
- console.log(err.message); // Prints 'Welcome to an imperfect world, CLU'
- }
-}
-```
-
-The exception will be an instance of the `Call.RuntimeError` class from the wails runtime,
-hence you can also test its type like this:
-
-```js
-import { Call } from '@wailsio/runtime';
-
-try {
- // ...
-} catch (err) {
- if (err instanceof Call.RuntimeError) {
- // ...
- }
-}
-```
-
-If the Go error value supports JSON marshaling, the exception's `cause` property
-will hold the marshaled version of the error:
-
-```go
-type ImperfectWorldError struct {
- Name string `json:"name"`
-}
-
-func (err *ImperfectWorldError) Error() {
- return fmt.Sprintf("Welcome to an imperfect world, %s", err.Name)
-}
-
-func (*MyService) FailingMethod(name string) error {
- return &ImperfectWorldError{
- Name: name,
- }
-}
-```
-
-```js
-import { MyService } from './bindings/changeme';
-
-try {
- await MyService.FailingMethod("CLU")
-} catch (err) {
- if (err.name === 'RuntimeError') {
- console.log(err.cause.name); // Prints 'CLU'
- }
-}
-```
-
-Generally, many Go error values will only have limited or no support for marshaling to JSON.
-If you so wish, you can customise the value provided as cause
-by specifying either a global or per-service error marshaling function:
-
-```go
-app := application.New(application.Options{
- MarshalError: func(err error) []byte {
- // ...
- },
- Services: []application.Service{
- application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
- MarshalError: func(err error) []byte {
- // ...
- },
- }),
- },
-})
-```
-
-Per-service functions override the global function,
-which in turn overrides the default behaviour of using `json.Marshal`.
-If a marshaling function returns `nil`, it falls back to the outer function:
-per-service functions fall back to the global function,
-which in turn falls back to the default behaviour.
-
-:::tip
-If you wish to omit the `cause` property on the resulting exception,
-let the marshaling function return a falsy JSON value like `[]byte("null")`.
-:::
-
-Here's an example marshaling function that unwraps path errors and reports the file path:
-
-```go
-app := application.New(application.Options{
- MarshalError: func(err error) []byte {
- var perr *fs.PathError
- if !errors.As(err, &perr) {
- // Not a path error, fall back to default handling.
- return nil
- }
-
- // Marshal path string
- path, err := json.Marshal(&perr.Path)
- if err != nil {
- // String marshaling failed, fall back to default handling.
- return nil
- }
-
- return []byte(fmt.Sprintf(`{"path":%s}`, path))
- },
-})
-```
-
-:::note
-Error marshaling functions are not allowed to fail.
-If they are not able to process a given error and return valid JSON,
-they should return `nil` and fall back to a more generic handler.
-If no strategy succeeds, the exception will not have a `cause` property.
-:::
-
-Binding call promises may also reject with a `TypeError`
-when the method has been passed the wrong number of arguments,
-when the conversion of arguments from JSON to their Go types fails,
-or when the conversion of results to JSON fails.
-These problems will usually be caught early by the type system.
-If your code typechecks but you still get type errors,
-it might be that some of your Go types are not supported by the `encoding/json` package:
-look for warnings from the binding generator to catch these.
-
-:::caution
-If you see a `ReferenceError` complaining about unknown methods,
-it could mean that your JS bindings have gotten out of sync with Go code
-and must be regenerated.
-:::
diff --git a/docs/src/content/docs/learn/clipboard.mdx b/docs/src/content/docs/learn/clipboard.mdx
deleted file mode 100644
index a31408dc7..000000000
--- a/docs/src/content/docs/learn/clipboard.mdx
+++ /dev/null
@@ -1,135 +0,0 @@
----
-title: Clipboard
-sidebar:
- order: 50
----
-
-The Wails Clipboard API provides a simple interface for interacting with the system clipboard. It allows you to read from and write to the clipboard, whilst supporting text data.
-
-## Accessing the Clipboard
-
-The clipboard can be accessed through the application instance:
-
-```go
-clipboard := app.Clipboard
-```
-
-## Setting Text
-
-To set text to the clipboard, utilise the `SetText` method:
-
-```go
-success := app.Clipboard.SetText("Hello World")
-if !success {
- // Handle error
-}
-```
-
-The `SetText` method returns a boolean indicating whether the operation was successful.
-
-:::tip[Empty Text]
-Setting an empty string (`""`) effectively clears the text content from the clipboard.
-:::
-
-## Getting Text
-
-To retrieve text from the clipboard, utilise the `Text` method:
-
-```go
-text, ok := app.Clipboard.Text()
-if !ok {
- // Handle error
-} else {
- // Use the text
-}
-```
-
-The `Text` method returns two values:
-- The text content from the clipboard (string)
-- A boolean indicating whether the operation was successful
-
-:::note[Platform Behaviour]
-The clipboard behaviour might vary slightly amongst operating systems. Always check the return values to ensure operations were successful.
-:::
-
-## Example
-
-Here's a complete example showing how to create a menu-driven application that demonstrates clipboard operations:
-
-```go
-package main
-
-import (
- "log"
- "runtime"
- "time"
-
- "github.com/wailsapp/wails/v3/pkg/application"
-)
-
-func main() {
- app := application.New(application.Options{
- Name: "Clipboard Demo",
- Description: "A demo of the clipboard API",
- Assets: application.AlphaAssets,
- })
-
- // Create a custom menu
- menu := app.Menu.New()
- if runtime.GOOS == "darwin" {
- menu.AddRole(application.AppMenu)
- }
-
- // Add clipboard operations to menu
- setClipboardMenu := menu.AddSubmenu("Set Clipboard")
- setClipboardMenu.Add("Set Text 'Hello'").OnClick(func(ctx *application.Context) {
- success := app.Clipboard.SetText("Hello")
- if !success {
- app.Dialog.Info().SetMessage("Failed to set clipboard text").Show()
- }
- })
-
- getClipboardMenu := menu.AddSubmenu("Get Clipboard")
- getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) {
- result, ok := app.Clipboard.Text()
- if !ok {
- app.Dialog.Info().SetMessage("Failed to get clipboard text").Show()
- } else {
- app.Dialog.Info().SetMessage("Got:\n\n" + result).Show()
- }
- })
-
- clearClipboardMenu := menu.AddSubmenu("Clear Clipboard")
- clearClipboardMenu.Add("Clear Text").OnClick(func(ctx *application.Context) {
- success := app.Clipboard.SetText("")
- if success {
- app.Dialog.Info().SetMessage("Clipboard text cleared").Show()
- } else {
- app.Dialog.Info().SetMessage("Clipboard text not cleared").Show()
- }
- })
-
- app.Menu.Set(menu)
- app.Window.New()
-
- err := app.Run()
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-```
-
-:::danger[Warning]
-Always handle clipboard operation failures gracefully, as they can fail due to various system-level reasons such as permissions or resource constraints.
-:::
-
-## Best Practices
-
-1. Always check the return values of clipboard operations
-2. Handle failures gracefully with appropriate user feedback
-3. Clear sensitive data from the clipboard when your application exits if it was responsible for putting it there
-4. Consider implementing a timeout mechanism for clipboard operations in critical sections of your application
-
-:::tip[Pro Tip]
-Whilst working with the clipboard in a production environment, consider implementing retry logic for critical clipboard operations, as they can occasionally fail due to temporary system conditions.
-:::
diff --git a/docs/src/content/docs/learn/context-menu.mdx b/docs/src/content/docs/learn/context-menu.mdx
deleted file mode 100644
index 8d70822cb..000000000
--- a/docs/src/content/docs/learn/context-menu.mdx
+++ /dev/null
@@ -1,237 +0,0 @@
----
-title: Context Menus
-sidebar:
- order: 51
----
-
-import { Tabs, TabItem } from "@astrojs/starlight/components";
-
-Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element.
-
-## Creating a Context Menu
-
-To create a context menu, use the `Add` method from the ContextMenu manager:
-
-```go
-contextMenu := app.ContextMenu.Add("menu-id", application.NewContextMenu())
-```
-
-The `menu-id` parameter is a unique identifier for the menu that will be used to associate it with HTML elements.
-
-## Adding Menu Items
-
-You can add items to your context menu using the same methods as application menus. Here's a simple example:
-
-```go
-contextMenu := application.NewContextMenu()
-contextMenu.Add("Cut").OnClick(func(ctx *application.Context) {
- // Handle cut action
-})
-contextMenu.Add("Copy").OnClick(func(ctx *application.Context) {
- // Handle copy action
-})
-contextMenu.Add("Paste").OnClick(func(ctx *application.Context) {
- // Handle paste action
-})
-
-// Register the context menu with the manager
-app.ContextMenu.Add("editor-menu", contextMenu)
-```
-
-:::tip[Menu Items]
-For detailed information about available menu item types and properties, refer to the [Menu Reference](./menu-reference) documentation.
-:::
-
-## Context Data
-
-Context menus can receive data from the HTML element that triggered them. This data can be accessed in the click handlers:
-
-```go
-contextMenu := app.ContextMenu.New()
-menuItem := contextMenu.Add("Process Image")
-menuItem.OnClick(func(ctx *application.Context) {
- imageID := ctx.ContextMenuData()
- // Process the image using the ID
-})
-
-// Register the context menu with the manager
-app.ContextMenu.Add("image-menu", contextMenu)
-```
-
-## Associating with HTML Elements
-
-To associate a context menu with an HTML element, use the `--custom-contextmenu` and `--custom-contextmenu-data` CSS properties:
-
-```html
-
- Right click me!
-
-```
-
-- `--custom-contextmenu`: Specifies the menu ID (must match the ID used in `NewContextMenu`)
-- `--custom-contextmenu-data`: Optional data that will be passed to the click handlers
-
-:::note
-This feature will only work as expected after the runtime [has been initialised](../runtime#initialisation).
-:::
-
-## Default Context Menu
-
-The default context menu is the webview's built-in context menu that provides system-level operations. You can control its visibility using the `--default-contextmenu` CSS property:
-
-```html
-
-
- No default menu here
-
-
-
-
- Default menu always shown
-
-
-
-
- Shows menu when appropriate
-
-```
-
-:::note[Smart Context Menu]
-The `auto` setting enables "smart" context menu behaviour:
-- Shows when text is selected
-- Shows in text input fields
-- Shows in editable content
-- Hides in other contexts
-:::
-
-## Updating Menu Items
-
-Menu items can be updated dynamically using the `SetLabel` method and other property setters. After making changes, call `Update` on the menu to apply them:
-
-```go
-contextMenu := application.NewContextMenu()
-menuItem := contextMenu.Add("Initial Label")
-
-// Register the context menu with the manager
-app.ContextMenu.Add("dynamic-menu", contextMenu)
-
-// Later, update the menu item
-menuItem.SetLabel("New Label")
-contextMenu.Update()
-```
-
-## Platform Considerations
-
-
-
-
- On macOS, context menus follow system conventions:
-
- - Menus use native system animations and transitions
- - Right-click is automatically mapped to Control+Click
- - Menu styling automatically adapts to system appearance
- - Standard text operations appear in the default context menu
- - Context menus support native macOS scrolling behaviour
-
-
-
-
-
- On Windows, context menus integrate with the Windows UI:
-
- - Menus use the Windows native context menu style
- - Right-click handling is native
- - Menu appearance follows the Windows theme
- - Default context menu includes standard Windows operations
- - Context menus support Windows touch and pen input
-
-
-
-
-
- On Linux, context menu behaviour varies by desktop environment:
-
- - Menu styling adapts to the current desktop theme
- - Right-click behaviour follows system settings
- - Default context menu content may vary by environment
- - Menu positioning follows desktop environment conventions
- - GTK/Qt integration depends on the environment
-
-
-
-
-:::tip[Pro Tip]
-Consider using different context menus for different types of elements in your application. This allows you to provide context-specific actions that make sense for each element type.
-:::
-
-## Best Practices
-
-1. Keep context menus focused and relevant to the clicked element
-2. Use clear, concise labels for menu items
-3. Group related items together
-4. Consider using separators to organise menu items
-5. Provide keyboard shortcuts for common actions
-6. Update menu items dynamically based on application state
-7. Handle errors gracefully when processing context data
-
-:::danger[Warning]
-Always validate context data received from the frontend before using it in your application logic, as it could be manipulated by users.
-:::
-
-## Example
-
-Here's a complete example demonstrating context menu features:
-
-```go
-package main
-
-import (
- "github.com/wailsapp/wails/v3/pkg/application"
-)
-
-func main() {
- app := application.New(application.Options{
- Name: "Context Menu Demo",
- })
-
- // Create a context menu
- contextMenu := app.ContextMenu.New()
-
- // Add items that respond to context data
- clickMe := contextMenu.Add("Click to show context data")
- dataLabel := contextMenu.Add("Current data: None")
-
- clickMe.OnClick(func(ctx *application.Context) {
- data := ctx.ContextMenuData()
- dataLabel.SetLabel("Current data: " + data)
- contextMenu.Update()
- })
-
- // Register the context menu with the manager
- app.ContextMenu.Add("test", contextMenu)
-
- window := app.Window.New()
- window.SetTitle("Context Menu Demo")
-
- err := app.Run()
- if err != nil {
- panic(err)
- }
-}
-```
-
-Associated HTML:
-
-```html
-
- Right click me to see the custom menu!
-
-
-
- No context menu here
-
-
-
-
Select this text to see the default menu
-
-
diff --git a/docs/src/content/docs/learn/dialogs.mdx b/docs/src/content/docs/learn/dialogs.mdx
deleted file mode 100644
index 783f7bf8b..000000000
--- a/docs/src/content/docs/learn/dialogs.mdx
+++ /dev/null
@@ -1,232 +0,0 @@
----
-title: Dialogs
-sidebar:
- order: 54
----
-
-import { Tabs, TabItem } from "@astrojs/starlight/components";
-
-Wails provides a comprehensive dialog system for displaying native system dialogs. These include informational messages, questions, file selection, and more.
-
-## Dialog Types
-
-### Information Dialog
-
-Display simple informational messages to users:
-
-```go
-dialog := app.Dialog.Info()
-dialog.SetTitle("Welcome")
-dialog.SetMessage("Welcome to our application!")
-dialog.Show()
-```
-
-### Question Dialog
-
-Present users with questions and customisable buttons:
-
-```go
-dialog := app.Dialog.Question()
-dialog.SetTitle("Save Changes")
-dialog.SetMessage("Do you want to save your changes?")
-dialog.AddButton("Save").OnClick(func() {
- // Handle save
-})
-saveButton := dialog.AddButton("Don't Save")
-dialog.SetDefaultButton(saveButton)
-dialog.Show()
-```
-
-### Error Dialog
-
-Display error messages:
-
-```go
-dialog := app.Dialog.Error()
-dialog.SetTitle("Error")
-dialog.SetMessage("Failed to save file")
-dialog.Show()
-```
-
-### File Dialogs
-
-#### Open File Dialog
-
-Allow users to select files to open:
-
-```go
-dialog := app.Dialog.OpenFile()
-dialog.SetTitle("Select Image")
-dialog.SetFilters([]*application.FileFilter{
- {
- DisplayName: "Images (*.png;*.jpg)",
- Pattern: "*.png;*.jpg",
- },
-})
-
-// Single file selection
-if path, err := dialog.PromptForSingleSelection(); err == nil {
- // Use selected file path
-}
-
-// Multiple file selection
-if paths, err := dialog.PromptForMultipleSelection(); err == nil {
- // Use selected file paths
-}
-```
-
-#### Save File Dialog
-
-Allow users to choose where to save files:
-
-```go
-dialog := app.Dialog.SaveFile()
-dialog.SetTitle("Save Document")
-dialog.SetDefaultFilename("document.txt")
-dialog.SetFilters([]*application.FileFilter{
- {
- DisplayName: "Text Files (*.txt)",
- Pattern: "*.txt",
- },
-})
-
-if path, err := dialog.PromptForSingleSelection(); err == nil {
- // Save file to selected path
-}
-```
-
-## Dialog Customisation
-
-### Setting Icons
-
-Dialogs can use custom icons from the built-in icon set:
-
-```go
-dialog := app.Dialog.Info()
-dialog.SetIcon(icons.ApplicationDarkMode256)
-```
-
-### Window Attachment
-
-Dialogs can be attached to specific windows:
-
-```go
-dialog := app.Dialog.Question()
-dialog.AttachToWindow(app.Window.Current())
-dialog.Show()
-```
-
-### Button Customisation
-
-Create buttons with custom labels and actions:
-
-```go
-dialog := app.Dialog.Question()
-dialog.SetMessage("Choose an action")
-
-// Add buttons with custom handlers
-dialog.AddButton("Save").OnClick(func() {
- // Handle save
-})
-dialog.AddButton("Don't Save").OnClick(func() {
- // Handle don't save
-})
-cancelButton := dialog.AddButton("Cancel")
-dialog.SetDefaultButton(cancelButton) // Set default button
-```
-
-## Platform Considerations
-
-
-
-
- On macOS, dialogs follow system conventions:
-
- - Use system-standard dialog appearances
- - Support keyboard navigation (Tab, Space, Return)
- - Support standard keyboard shortcuts (⌘+.)
- - Automatically handle dark/light mode
- - Support system accessibility features
- - Position relative to parent window
-
-
-
-
-
- On Windows, dialogs integrate with the Windows UI:
-
- - Use Windows system dialog styles
- - Support keyboard navigation (Tab, Space, Enter)
- - Support Windows accessibility features
- - Follow Windows dialog positioning rules
- - Adapt to Windows theme settings
- - Support high DPI displays
-
-
-
-
-
- On Linux, dialog behaviour depends on the desktop environment:
-
- - Use native dialog widgets when available
- - Follow desktop environment theme
- - Support keyboard navigation
- - Adapt to desktop environment settings
- - Position according to window manager rules
- - Support desktop environment accessibility
-
-
-
-
-## Directory Selection
-
-Allow users to select directories:
-
-```go
-dialog := app.Dialog.OpenFile()
-dialog.CanChooseDirectories(true)
-dialog.CanChooseFiles(false)
-dialog.SetTitle("Select Project Directory")
-if path, err := dialog.PromptForSingleSelection(); err == nil {
- // Use selected directory path
-}
-```
-
-## About Dialog
-
-Display application information:
-
-```go
-app.Menu.ShowAbout()
-```
-
-## Best Practices
-
-1. Use appropriate dialog types for different scenarios:
- - InfoDialog for general messages
- - QuestionDialog for user decisions
- - ErrorDialog for error messages
- - FileDialog for file operations
-
-2. Provide clear and concise messages:
- - Use descriptive titles
- - Keep messages brief but informative
- - Clearly state any required user action
-
-3. Handle dialog responses appropriately:
- - Check for errors in file dialogs
- - Provide feedback for user actions
- - Handle cancellation gracefully
-
-4. Consider platform conventions:
- - Follow platform-specific dialog patterns
- - Use appropriate button ordering
- - Respect system settings
-
-:::tip[Pro Tip]
-When using file dialogs, always set appropriate filters to help users select the correct file types for your application.
-:::
-
-:::danger[Warning]
-Always handle potential errors from file and directory dialogs, as they may fail due to permissions or other system issues.
-:::
diff --git a/docs/src/content/docs/learn/events.mdx b/docs/src/content/docs/learn/events.mdx
deleted file mode 100644
index 394d1e03e..000000000
--- a/docs/src/content/docs/learn/events.mdx
+++ /dev/null
@@ -1,778 +0,0 @@
----
-title: Events
-sidebar:
- order: 55
----
-
-import { Tabs, TabItem } from "@astrojs/starlight/components";
-
-Wails provides a flexible event system that enables communication between different parts of your application. This includes both application-level and window-level events.
-
-## Application Events
-
-Application events are triggered by application-level state changes such as application startup, theme changes, and power events. You can listen for these events using the `OnApplicationEvent` method:
-
-```go
-app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(event *application.ApplicationEvent) {
- app.Logger.Info("Application started!")
-})
-
-app.Event.OnApplicationEvent(events.Windows.SystemThemeChanged, func(event *application.ApplicationEvent) {
- app.Logger.Info("System theme changed!")
- if event.Context().IsDarkMode() {
- app.Logger.Info("System is now using dark mode!")
- } else {
- app.Logger.Info("System is now using light mode!")
- }
- })
-```
-
-### Common Application Events
-
-Common application events are aliases for platform-specific application events. These events are triggered by application-level state
-changes such as application startup, theme changes, and power events.
-
-Here is the same example as above, but using common application events to make it work across all platforms:
-
-```go
-app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) {
- app.Logger.Info("Application started!")
-})
-
-app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
- if event.Context().IsDarkMode() {
- app.Logger.Info("System is now using dark mode!")
- } else {
- app.Logger.Info("System is now using light mode!")
- }
-})
-```
-#### Common Application Event List
-
-| Event Name | Description |
-|---------------------------|----------------------------------------------------------------------------------------------------------|
-| ApplicationOpenedWithFile | Application opened with a file. See [File Associations](/guides/file-associations) for more information. |
-| ApplicationStarted | Application has started |
-| ThemeChanged | System theme changed |
-
-### Platform-Specific Application Events
-
-Below is a list of all platform-specific application events.
-
-
-
-
- | Event Name | Common Event | Description |
- |------------|--------------|-------------|
- | ApplicationDidBecomeActive | - | Application became active |
- | ApplicationDidChangeBackingProperties | - | Application backing properties changed |
- | ApplicationDidChangeEffectiveAppearance | ThemeChanged | Application appearance changed |
- | ApplicationDidChangeIcon | - | Application icon changed |
- | ApplicationDidChangeOcclusionState | - | Application occlusion state changed |
- | ApplicationDidChangeScreenParameters | - | Screen parameters changed |
- | ApplicationDidChangeStatusBarFrame | - | Status bar frame changed |
- | ApplicationDidChangeStatusBarOrientation | - | Status bar orientation changed |
- | ApplicationDidChangeTheme | ThemeChanged | System theme changed |
- | ApplicationDidFinishLaunching | ApplicationStarted | Application finished launching |
- | ApplicationDidHide | - | Application hidden |
- | ApplicationDidResignActiveNotification | - | Application resigned active state |
- | ApplicationDidUnhide | - | Application unhidden |
- | ApplicationDidUpdate | - | Application updated |
- | ApplicationShouldHandleReopen | - | Application should handle reopen |
- | ApplicationWillBecomeActive | - | Application will become active |
- | ApplicationWillFinishLaunching | - | Application will finish launching |
- | ApplicationWillHide | - | Application will hide |
- | ApplicationWillResignActiveNotification | - | Application will resign active state |
- | ApplicationWillTerminate | - | Application will terminate |
- | ApplicationWillUnhide | - | Application will unhide |
- | ApplicationWillUpdate | - | Application will update |
-
-
-
-
-
- | Event Name | Common Event | Description |
- |------------|--------------|-------------|
- | APMPowerSettingChange | - | Power settings changed |
- | APMPowerStatusChange | - | Power status changed |
- | APMResumeAutomatic | - | System resuming automatically |
- | APMResumeSuspend | - | System resuming from suspend |
- | APMSuspend | - | System suspending |
- | ApplicationStarted | ApplicationStarted | Application started |
- | SystemThemeChanged | ThemeChanged | System theme changed |
-
-
-
- | Event Name | Common Event | Description |
- |------------|--------------|-------------|
- | ApplicationStartup | ApplicationStarted | Application started |
- | SystemThemeChanged | ThemeChanged | System theme changed |
-
-
-
-## Window Events
-
-Window events are triggered by window-specific actions such as resizing, moving, or changing focus state. You can listen for these events using the `OnWindowEvent` method:
-
-```go
-window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
- app.Logger.Info("Window is closing!")
-})
-
-window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
- app.Logger.Info("Window gained focus!")
-})
-```
-
-### Hooks vs Standard Listeners
-
-Wails provides two ways to handle window events: standard listeners (OnWindowEvent) and hooks (RegisterHook). The key differences are:
-
-1. **Execution Order**: Hooks are executed first and in the order they are registered, while standard listeners execute after Hooks and have no guaranteed order.
-2. **Blocking**: Hooks are blocking and must complete before the next hook is executed. Standard listeners are non-blocking.
-3. **Event Cancellation**: When cancelling an event in a Hook, it prevents it from propagating further. This is useful to prevent
-default behaviour, such as closing a window. Cancelling an event in a standard listener will only prevent it from being emitted
-from that point in time.
-
-In this example, the window will only close after the close button has been clicked three times, demonstrating how hooks can be used to control event flow.
-
-```go
-// Hook - runs synchronously. The window will not close until the countdown reaches zero.
-var countdown = 3
-window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
- countdown--
- if countdown == 0 {
- app.Logger.Info("Window closing - countdown reached zero!")
- return
- }
- app.Logger.Info("Preventing window from closing - countdown:", countdown)
- e.Cancel()
-})
-```
-
-This next example demonstrates the execution order of hooks vs standard listeners.
-
-```go
-window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
- app.Logger.Info("I always run after hooks!")
-})
-
-// Multiple hooks are executed in order
-window.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) {
- app.Logger.Info("First focus hook - will always run first!")
-})
-window.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) {
- app.Logger.Info("Second focus hook - will always run second!")
-})
-```
-
-This produces the following output:
-
-```
-INF First focus hook - will always run first!
-INF Second focus hook - will always run second!
-INF I always run after hooks!
-```
-
-### Common Window Events
-
-| Event Name | Description |
-|--------------------|---------------------------|
-| WindowClosing | Window is closing |
-| WindowDidMove | Window moved |
-| WindowDidResize | Window resized |
-| WindowDPIChanged | Window DPI changed |
-| WindowFilesDropped | Files dropped on window |
-| WindowFocus | Window gained focus |
-| WindowFullscreen | Window entered fullscreen |
-| WindowHide | Window hidden |
-| WindowLostFocus | Window lost focus |
-| WindowMaximise | Window maximised |
-| WindowMinimise | Window minimised |
-| WindowRestore | Window restored |
-| WindowRuntimeReady | Window runtime is ready |
-| WindowShow | Window shown |
-| WindowUnFullscreen | Window exited fullscreen |
-| WindowUnMaximise | Window unmaximised |
-| WindowUnMinimise | Window unminimised |
-| WindowZoom | Window zoomed |
-| WindowZoomIn | Window zoomed in |
-| WindowZoomOut | Window zoomed out |
-| WindowZoomReset | Window zoom reset |
-
-### Enhanced Drag and Drop with Targeted Dropzones
-
-Wails v3 introduces an enhanced drag-and-drop system that allows you to define specific "dropzones" within your application's HTML. This provides finer control over where files can be dropped and offers automatic visual feedback managed by the Wails runtime.
-
-#### 1. Defining Dropzones in HTML
-
-To designate an HTML element as a dropzone, add the `data-wails-dropzone` attribute to it. Any element with this attribute will become a valid target for file drops.
-
-**Example:**
-```html
-
-
-
- Another drop target
-
-
-
-
- 📁 Documents
-
-```
-
-#### 2. Visual Feedback
-
-When files are dragged over an element marked with `data-wails-dropzone`, the Wails JavaScript runtime automatically adds the `wails-dropzone-hover` CSS class to that element. You can define styles for this class to provide visual feedback:
-
-**Example CSS:**
-```css
-/* Base style for all dropzones (resting state) */
-.dropzone {
- border: 2px dashed #888;
- background-color: #303030;
- padding: 20px;
- text-align: center;
-}
-
-/* Default hover effect applied by the runtime */
-.dropzone.wails-dropzone-hover {
- background-color: #3c3c3e;
- border-style: dotted;
- border-color: #007bff;
- box-shadow: 0 0 8px rgba(0, 123, 255, 0.4);
-}
-
-/* Example: Customizing hover for a specific dropzone to override the default */
-#myDropArea.wails-dropzone-hover {
- background-color: lightgreen;
- outline: 2px solid green;
-}
-```
-The runtime handles adding and removing the `wails-dropzone-hover` class as files are dragged in and out of the dropzone or the window.
-
-#### 3. Handling Drops in Go
-
-On the Go side, listen for the `events.Common.WindowDropZoneFilesDropped` event. This event will be emitted when files are dropped onto an element that has the `data-wails-dropzone` attribute.
-
-**Example Go Handler:**
-```go
-import (
- "fmt"
- "log"
- "github.com/wailsapp/wails/v3/pkg/application"
- "github.com/wailsapp/wails/v3/pkg/events"
-)
-
-// Assuming 'win' is your *application.WebviewWindow instance
-win.OnWindowEvent(events.Common.WindowDropZoneFilesDropped, func(event *application.WindowEvent) {
- droppedFiles := event.Context().DroppedFiles()
- log.Printf("Files dropped: %v", droppedFiles)
-
- details := event.Context().DropZoneDetails()
- if details != nil {
- log.Printf("Dropped on Element ID: '%s'", details.ElementID)
- log.Printf("Element Classes: %v", details.ClassList)
- log.Printf("Drop Coordinates (relative to window): X=%d, Y=%d", details.X, details.Y)
-
- // Access custom data attributes from the HTML element
- if folderName, exists := details.Attributes["data-folder-name"]; exists {
- log.Printf("Folder name: %s", folderName)
- }
- if folderPath, exists := details.Attributes["data-path"]; exists {
- log.Printf("Target path: %s", folderPath)
- }
-
- // Example: Handle different dropzone types based on ElementID
- switch details.ElementID {
- case "documents":
- log.Printf("Files dropped on Documents folder")
- // Handle document uploads
- case "downloads":
- log.Printf("Files dropped on Downloads folder")
- // Handle download folder drops
- case "trash":
- log.Printf("Files dropped on Trash")
- // Handle file deletion
- default:
- log.Printf("Files dropped on unknown target: %s", details.ElementID)
- }
-
- payload := map[string]interface{}{
- "files": droppedFiles,
- "targetID": details.ElementID,
- "targetClasses": details.ClassList,
- "dropX": details.X,
- "dropY": details.Y,
- "attributes": details.Attributes,
- }
- application.Get().Event.Emit("frontend:FileDropInfo", payload) // Emits globally
- // or win.EmitEvent("frontend:FileDropInfoForWindow", payload) // Emits to this specific window
- } else {
- log.Println("Drop occurred, but DropZoneDetails were nil.")
- }
-})
-```
-The `event.Context().DropZoneDetails()` method returns a pointer to an `application.DropZoneDetails` struct (or `nil` if details aren't available), containing:
-- `ElementID string`: The id of the element dropped onto
-- `ClassList []string`: The list of CSS classes of the HTML element that received the drop.
-- `X int`: The X-coordinate of the drop, relative to the window's content area.
-- `Y int`: The Y-coordinate of the drop, relative to the window's content area.
-- `Attributes map[string]string`: A map containing all HTML attributes of the target element, allowing access to custom data attributes like `data-path`, `data-folder-name`, etc.
-
-The `event.Context().DroppedFiles()` method returns a `[]string` of file paths.
-
-For a fully runnable demonstration of these features, including multiple styled dropzones, please refer to the example located in the `v3/examples/drag-n-drop` directory within the Wails repository.
-
-### Platform-Specific Window Events
-
-
-
- | Event Name | Common Event | Description |
- |------------|--------------|-------------|
- | WindowDidBecomeKey | WindowFocus | Window became key window |
- | WindowDidBecomeMain | - | Window became main window |
- | WindowDidBeginSheet | - | Sheet began |
- | WindowDidChangeAlpha | - | Window alpha changed |
- | WindowDidChangeBackingLocation | - | Window backing location changed |
- | WindowDidChangeBackingProperties | - | Window backing properties changed |
- | WindowDidChangeCollectionBehavior | - | Window collection behaviour changed |
- | WindowDidChangeEffectiveAppearance | - | Window appearance changed |
- | WindowDidChangeOcclusionState | - | Window occlusion state changed |
- | WindowDidChangeOrderingMode | - | Window ordering mode changed |
- | WindowDidChangeScreen | - | Window screen changed |
- | WindowDidChangeScreenParameters | - | Window screen parameters changed |
- | WindowDidChangeScreenProfile | - | Window screen profile changed |
- | WindowDidChangeScreenSpace | - | Window screen space changed |
- | WindowDidChangeScreenSpaceProperties | - | Window screen space properties changed |
- | WindowDidChangeSharingType | - | Window sharing type changed |
- | WindowDidChangeSpace | - | Window space changed |
- | WindowDidChangeSpaceOrderingMode | - | Window space ordering mode changed |
- | WindowDidChangeTitle | - | Window title changed |
- | WindowDidChangeToolbar | - | Window toolbar changed |
- | WindowDidDeminiaturize | WindowUnMinimise | Window unminimised |
- | WindowDidEndSheet | - | Sheet ended |
- | WindowDidEnterFullScreen | WindowFullscreen | Window entered fullscreen |
- | WindowDidEnterVersionBrowser | - | Window entered version browser |
- | WindowDidExitFullScreen | WindowUnFullscreen | Window exited fullscreen |
- | WindowDidExitVersionBrowser | - | Window exited version browser |
- | WindowDidExpose | - | Window exposed |
- | WindowDidFocus | WindowFocus | Window gained focus |
- | WindowDidMiniaturize | WindowMinimise | Window minimised |
- | WindowDidMove | WindowDidMove | Window moved |
- | WindowDidOrderOffScreen | - | Window ordered off screen |
- | WindowDidOrderOnScreen | - | Window ordered on screen |
- | WindowDidResignKey | - | Window resigned key window |
- | WindowDidResignMain | - | Window resigned main window |
- | WindowDidResize | WindowDidResize | Window resized |
- | WindowDidUpdate | - | Window updated |
- | WindowDidUpdateAlpha | - | Window alpha updated |
- | WindowDidUpdateCollectionBehavior | - | Window collection behaviour updated |
- | WindowDidUpdateCollectionProperties | - | Window collection properties updated |
- | WindowDidUpdateShadow | - | Window shadow updated |
- | WindowDidUpdateTitle | - | Window title updated |
- | WindowDidUpdateToolbar | - | Window toolbar updated |
- | WindowDidZoom | WindowZoom | Window zoomed |
- | WindowFileDraggingEntered | - | File dragging entered window |
- | WindowFileDraggingExited | - | File dragging exited window |
- | WindowFileDraggingPerformed | - | File dragging performed |
- | WindowHide | WindowHide | Window hidden |
- | WindowMaximise | WindowMaximise | Window maximised |
- | WindowShouldClose | WindowClosing | Window should close |
- | WindowShow | WindowShow | Window shown |
- | WindowUnMaximize | WindowUnMaximise | Window unmaximised |
- | WindowZoomIn | WindowZoomIn | Window zoomed in |
- | WindowZoomOut | WindowZoomOut | Window zoomed out |
- | WindowZoomReset | WindowZoomReset | Window zoom reset |
- |------------|--------------|-------------|
-
-
-
- | Event Name | Common Event | Description |
- |------------|--------------|-------------|
- | WebViewNavigationCompleted | - | WebView navigation completed |
- | WindowActive | - | Window became active |
- | WindowBackgroundErase | - | Window background needs erasing |
- | WindowClickActive | - | Window clicked whilst active |
- | WindowClosing | WindowClosing | Window closing |
- | WindowDidMove | WindowDidMove | Window moved |
- | WindowDidResize | WindowDidResize | Window resized |
- | WindowEndMove | - | Window finished moving |
- | WindowEndResize | - | Window finished resising |
- | WindowFullscreen | WindowFullscreen | Window entered fullscreen |
- | WindowHide | WindowHide | Window hidden |
- | WindowInactive | - | Window became inactive |
- | WindowKillFocus | WindowLostFocus | Window lost focus |
- | WindowMaximise | WindowMaximise | Window maximised |
- | WindowMinimise | WindowMinimise | Window minimised |
- | WindowPaint | - | Window needs painting |
- | WindowRestore | WindowRestore | Window restored |
- | WindowSetFocus | WindowFocus | Window gained focus |
- | WindowShow | WindowShow | Window shown |
- | WindowStartMove | - | Window started moving |
- | WindowStartResize | - | Window started resising |
- | WindowUnFullscreen | WindowUnFullscreen | Window exited fullscreen |
- | WindowUnMaximise | WindowUnMaximise | Window unmaximised |
- | WindowUnMinimise | WindowUnMinimise | Window unminimised |
- | WindowZOrderChanged | - | Window z-order changed |
-
- #### Input Events
- | Event Name | Description |
- |------------|-------------|
- | WindowDragDrop | Files dragged and dropped |
- | WindowDragEnter | Drag entered window |
- | WindowDragLeave | Drag left window |
- | WindowDragOver | Drag over window |
- | WindowKeyDown | Key pressed |
- | WindowKeyUp | Key released |
- | WindowNonClientHit | Mouse hit in non-client area |
- | WindowNonClientMouseDown | Mouse down in non-client area |
- | WindowNonClientMouseLeave | Mouse left non-client area |
- | WindowNonClientMouseMove | Mouse move in non-client area |
- | WindowNonClientMouseUp | Mouse up in non-client area |
-
-
-
- | Event Name | Common Event | Description |
- |------------|--------------|-------------|
- | WindowDeleteEvent | WindowClosing | Window delete requested |
- | WindowDidMove | WindowDidMove | Window moved |
- | WindowDidResize | WindowDidResize | Window resized |
- | WindowFocusIn | WindowFocus | Window gained focus |
- | WindowFocusOut | WindowLostFocus | Window lost focus |
- | WindowLoadChanged | WindowShow | Window load state changed |
-
-
-
-## Menu Events
-
-Menu events are triggered by menu-specific actions such as opening, closing, and interacting with menu items. These events are useful for creating dynamic menus and responding to menu interactions.
-
-```go
-// Listen for menu events
-app.Event.OnApplicationEvent(events.Mac.MenuDidOpen, func(event *application.ApplicationEvent) {
- app.Logger.Info("Menu opened!")
-})
-
-app.Event.OnApplicationEvent(events.Mac.MenuWillSendAction, func(event *application.ApplicationEvent) {
- app.Logger.Info("Menu about to send action!")
-})
-```
-
-For more information about menus, see the [Application Menu](/learn/application-menu) and [Context Menu](/learn/context-menu) documentation.
-
-### Platform-Specific Menu Events
-
-
-
- | Event Name | Description |
- |------------|-------------|
- | MenuDidAddItem | Menu item added |
- | MenuDidBeginTracking | Menu began tracking |
- | MenuDidClose | Menu closed |
- | MenuDidDisplayItem | Menu item displayed |
- | MenuDidEndTracking | Menu ended tracking |
- | MenuDidHighlightItem | Menu item highlighted |
- | MenuDidOpen | Menu opened |
- | MenuDidPopUp | Menu popped up |
- | MenuDidRemoveItem | Menu item removed |
- | MenuDidSendAction | Menu sent action |
- | MenuDidSendActionToItem | Menu sent action to item |
- | MenuDidUpdate | Menu updated |
- | MenuWillAddItem | Menu will add item |
- | MenuWillBeginTracking | Menu will begin tracking |
- | MenuWillDisplayItem | Menu will display item |
- | MenuWillEndTracking | Menu will end tracking |
- | MenuWillHighlightItem | Menu will highlight item |
- | MenuWillOpen | Menu will open |
- | MenuWillPopUp | Menu will pop up |
- | MenuWillRemoveItem | Menu will remove item |
- | MenuWillSendAction | Menu will send action |
- | MenuWillSendActionToItem | Menu will send action to item |
- | MenuWillUpdate | Menu will update |
-
-
-
- Windows does not currently provide specific menu events.
-
-
-
- Linux does not currently provide specific menu events.
-
-
-
-## WebView Events
-
-WebView events are triggered by navigation and loading state changes in the WebView component. These events are useful for tracking page loads and navigation state.
-
-```go
-// Listen for WebView navigation events
-app.Event.OnApplicationEvent(events.Mac.WebViewDidStartProvisionalNavigation, func(event *application.ApplicationEvent) {
- app.Logger.Info("WebView started loading a page!")
-})
-
-app.Event.OnApplicationEvent(events.Mac.WebViewDidFinishNavigation, func(event *application.ApplicationEvent) {
- app.Logger.Info("WebView finished loading the page!")
-})
-
-// On Windows
-app.Event.OnApplicationEvent(events.Windows.WebViewNavigationCompleted, func(event *application.ApplicationEvent) {
- app.Logger.Info("WebView navigation completed!")
-})
-```
-
-### Platform-Specific WebView Events
-
-
-
- | Event Name | Description |
- |------------|-------------|
- | WebViewDidCommitNavigation | Navigation committed |
- | WebViewDidFinishNavigation | Navigation finished |
- | WebViewDidReceiveServerRedirectForProvisionalNavigation | Server redirect received |
- | WebViewDidStartProvisionalNavigation | Provisional navigation started |
-
-
-
- | Event Name | Description |
- |------------|-------------|
- | WebViewNavigationCompleted | Navigation completed |
-
-
-
- Linux does not currently provide specific WebView events.
-
-
-
-## Custom Events
-
-You can emit and listen for custom events to enable communication between different parts of your application. Wails v3 provides both a traditional API and a new [Manager API](/learn/manager-api) for better organization.
-
-### Emitting Events
-
-You can emit custom events from anywhere in your application:
-
-
-
-```go
-// NEW: Using the Event Manager (recommended)
-app.Event.Emit("myevent", "hello")
-
-// Emit an event without data
-app.Event.Emit("datalessevent")
-
-// Emit from a specific window
-window.EmitEvent("windowevent", "window specific data")
-```
-
-
-```go
-// Traditional API (no longer works)
-app.EmitEvent("myevent", "hello")
-
-
-```
-
-
-
-### Handling Custom Events
-
-Listen for custom events using the event management methods:
-
-
-
-```go
-// NEW: Using the Event Manager (recommended)
-cancelFunc := app.Event.On("myevent", func(e *application.CustomEvent) {
- // Access event information
- name := e.Name // Event name
- data := e.Data // Event data
- sender := e.Sender // Name of the window sending the event, or "" if sent from application
- cancelled := e.IsCancelled() // Event cancellation status
-})
-
-// Remove specific event listener
-app.Event.Off("myevent")
-
-// Remove all event listeners
-app.Event.Reset()
-
-// Listen for events a limited number of times
-app.Event.OnMultiple("myevent", func(e *application.CustomEvent) {
- // This will only be called 3 times
-}, 3)
-```
-
-
-```go
-// Traditional API (still supported)
-cancelFunc := app.OnEvent("myevent", func(e *application.CustomEvent) {
- // Access event information
- name := e.Name // Event name
- data := e.Data // Event data
- sender := e.Sender // Name of the window sending the event, or "" if sent from application
- cancelled := e.IsCancelled() // Event cancellation status
-})
-
-// Remove specific event listener
-app.OffEvent("myevent")
-
-// Remove all event listeners
-app.ResetEvents()
-
-// Listen for events a limited number of times
-app.OnMultipleEvent("myevent", func(e *application.CustomEvent) {
- // This will only be called 3 times
-}, 3)
-```
-
-
-
-You can also register a hook that will run synchronously when the event is emitted.
-Hooks are useful [to cancel custom events and stop their propagation early](#event-cancellation):
-
-```go
-app.Event.RegisterHook("myevent", func(e *application.CustomEvent) {
- // Do something.
-})
-```
-
-### Custom Event Registration
-
-You can call the `application.RegisterEvent` function at init time to register custom event names:
-
-```go
-type MyEventData struct {
- Num int
- Text string
-}
-
-func init() {
- application.RegisterEvent[MyEventData]("myevent")
-}
-```
-
-:::caution
-Because this function is meant to be called at init time, it will panic when the arguments are not valid
-or the same event name is registered twice with two distinct data types.
-:::
-
-:::note
-It is safe to register the same event multiple times as long as the data type is always the same.
-This might be useful to ensure a certain event is registered as soon as any one of many packages is loaded.
-:::
-
-Once the event is registered, data arguments passed to the `Event.Emit` method will be checked against the specified type.
-In case of a mismatch, an error will be emitted and either logged or passed on to the registered error handled, if any.
-The offending event will not be propagated. Thanks to this mechanism, it is safe to assume that the data field
-of registered custom events will always be assignable to the declared type.
-
-:::tip[Strict mode]
-If you use the `strictevents` build tag, using unregistered events will trigger a warning message in development mode.
-The runtime emits at most one warning per event name, to avoid spamming the log.
-:::
-
-### Binding generator support
-
-The binding generator outputs TypeScript definitions and internal glue code
-to provide transparent support for registered events on the frontend.
-The specified data type will be rendered to TypeScript
-and the data field will be typed by the corresponding model:
-
-```js
-import { Events } from "@wailsio/runtime";
-
-Events.On("myevent", (ev) => {
- ev.data; // Has type MyEventData
- ev.data.Text; // Has type string
-})
-```
-
-:::caution
-Static typing of event data on the frontend will only be available when the event name
-is specified by a constant expression, as shown above.
-:::
-
-When using the `Events.On*/Events.Off*` family of runtime methods or the `WailsEvent` constructor,
-IDEs with TypeScript support will offer autocompletion for registered event names.
-
-You can look at built-in application templates
-for an example of how to configure your project for typed events.
-
-### Events without data
-
-The `application.Void` interface can be used to register events without associated data:
-
-```go
-func init() {
- application.RegisterEvent[application.Void]("dataless")
-}
-```
-
-On the Go side, the runtime will check that the data field for the `"dataless"` event is always `nil`.
-On the frontend, the binding generator will translate `application.Void` as the TypeScript `void` type.
-
-## Event Cancellation
-
-Events can be cancelled to prevent their default behaviour or stop propagation to other listeners. This is particularly useful for hooks that need to control window closing, menu actions, or other system events.
-
-### Cancelling Events
-
-To cancel an event, call the `Cancel()` method on the event object:
-
-```go
-window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
- // Prevent the window from closing
- e.Cancel()
-})
-
-// For custom events
-app.RegisterHook("myevent", func(e *application.CustomEvent) {
- e.Cancel()
-})
-```
-
-:::tip[Pro Tip]
-Remember that event cancellation in hooks affects all subsequent hooks and listeners, whilst cancellation in standard listeners only affects listeners that haven't yet been called.
-:::
-
-### Checking Event Cancellation
-
-You can check if an event has been cancelled using the `IsCancelled()` method:
-
-```go
-window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
- if e.IsCancelled() {
- app.Logger.Info("Window closing was cancelled by another hook")
- return
- }
- // Process event
-})
-
-// For custom events
-app.Event.On("myevent", func(e *application.CustomEvent) {
- if e.IsCancelled() {
- app.Logger.Info("Event was cancelled")
- return
- }
- // Process event
-})
-```
-
-When emitting a custom event, you can also check the value returned by the `Event.Emit` method (for the app and windows):
-
-```go
-if app.Event.Emit("myevent") {
- app.Logger.Info("Event was cancelled")
- return
-}
-```
-
-The returned value will take into account all hooks, but may or may not take into account cancellations that happen asynchronously in listeners.
diff --git a/docs/src/content/docs/learn/menu-reference.mdx b/docs/src/content/docs/learn/menu-reference.mdx
deleted file mode 100644
index b0659f061..000000000
--- a/docs/src/content/docs/learn/menu-reference.mdx
+++ /dev/null
@@ -1,188 +0,0 @@
----
-title: Menu Reference
-sidebar:
- order: 52
----
-
-This reference document covers the common menu item types and properties available in Wails v3. These features are shared between application menus and context menus.
-
-## Menu Item Types
-
-### Regular Menu Items
-
-The most basic type of menu item displays text and triggers an action when clicked:
-
-```go
-menuItem := menu.Add("Click Me")
-menuItem.OnClick(func(ctx *application.Context) {
- // Handle click
-})
-```
-
-### Checkboxes
-
-Checkbox menu items provide a toggleable state:
-
-```go
-checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked
-checkbox.OnClick(func(ctx *application.Context) {
- isChecked := ctx.ClickedMenuItem().Checked()
- // Handle state change
-})
-```
-
-### Radio Groups
-
-Radio items create mutually exclusive options. Items are grouped automatically when placed adjacently:
-
-```go
-menu.AddRadio("Option 1", true) // true = initially selected
-menu.AddRadio("Option 2", false)
-menu.AddRadio("Option 3", false)
-```
-
-### Submenus
-
-Submenus allow you to create nested menu structures:
-
-```go
-submenu := menu.AddSubmenu("More Options")
-submenu.Add("Submenu Item 1")
-submenu.Add("Submenu Item 2")
-```
-
-### Separators
-
-Separators add visual dividers between menu items:
-
-```go
-menu.Add("Item 1")
-menu.AddSeparator()
-menu.Add("Item 2")
-```
-
-## Menu Item Properties
-
-### Label
-
-The text displayed for the menu item:
-
-```go
-menuItem := menu.Add("Initial Label")
-menuItem.SetLabel("New Label")
-```
-
-### Enabled State
-
-Control whether the menu item can be interacted with:
-
-```go
-menuItem := menu.Add("Disabled Item")
-menuItem.SetEnabled(false)
-```
-
-### Checked State
-
-For checkbox and radio items, control or query their checked state:
-
-```go
-checkbox := menu.AddCheckbox("Feature", false)
-checkbox.SetChecked(true)
-isChecked := checkbox.Checked()
-```
-
-### Accelerators
-
-Add keyboard shortcuts to menu items:
-
-```go
-menuItem := menu.Add("Save")
-menuItem.SetAccelerator("CmdOrCtrl+S")
-```
-
-Common accelerator modifiers:
-- `CmdOrCtrl`: Command on macOS, Control on Windows/Linux
-- `Shift`
-- `Alt`: Option on macOS
-- `Ctrl`: Control key on all platforms
-
-## Event Handling
-
-### Click Events
-
-Handle menu item clicks using the `OnClick` method:
-
-```go
-menuItem.OnClick(func(ctx *application.Context) {
- // Access the clicked item
- clickedItem := ctx.ClickedMenuItem()
-
- // Get current state
- label := clickedItem.Label()
- isChecked := clickedItem.Checked()
-
- // Update the item
- clickedItem.SetLabel("New Label")
-})
-```
-
-### Shared Event Handlers
-
-Event handlers can be shared amongst multiple menu items:
-
-```go
-handleClick := func(ctx *application.Context) {
- item := ctx.ClickedMenuItem()
- // Common handling logic
-}
-
-menu.Add("Item 1").OnClick(handleClick)
-menu.Add("Item 2").OnClick(handleClick)
-```
-
-## Dynamic Updates
-
-Menu items can be updated dynamically during runtime:
-
-```go
-menuItem := menu.Add("Initial State")
-
-// Later, update the item
-menuItem.SetLabel("New Label")
-menuItem.SetEnabled(false)
-menuItem.SetChecked(true)
-
-// Apply changes
-menu.Update()
-```
-
-:::note[Update Required]
-After modifying menu items, call `Update()` on the parent menu to apply the changes.
-:::
-
-## Best Practices
-
-1. Use clear, concise labels that describe the action
-2. Group related items together using separators
-3. Limit submenu depth to maintain usability
-4. Provide keyboard shortcuts for common actions
-5. Keep radio groups focused on a single choice
-6. Update menu items to reflect application state
-7. Handle all possible states in click handlers
-
-:::tip[Pro Tip]
-When sharing event handlers, use the `ctx.ClickedMenuItem()` method to determine which item triggered the event and handle it accordingly.
-:::
-
-## Platform Considerations
-
-:::note[Platform Behaviour]
-Menu appearance and behaviour varies by platform:
-- macOS: Uses native menu styling and supports system roles
-- Windows: Follows Windows menu conventions
-- Linux: Adapts to the desktop environment's theme
-:::
-
-:::danger[Warning]
-Always test menu functionality across all supported platforms, as behaviour and appearance may vary significantly.
-:::
diff --git a/docs/src/content/docs/learn/runtime.mdx b/docs/src/content/docs/learn/runtime.mdx
deleted file mode 100644
index 7b66a06b3..000000000
--- a/docs/src/content/docs/learn/runtime.mdx
+++ /dev/null
@@ -1,91 +0,0 @@
----
-title: Runtime
-sidebar:
- order: 30
----
-
-The Wails runtime is the standard library for Wails applications. It provides a
-number of features that may be used in your applications, including:
-
-- Window management
-- Dialogs
-- Browser integration
-- Clipboard
-- Menus
-- System information
-- Events
-- Context Menus
-- Screens
-- WML (Wails Markup Language)
-
-The runtime is required for integration between Go and the frontend. There are 2
-ways to integrate the runtime:
-
-- Using the `@wailsio/runtime` package
-- Using a pre-built bundle
-
-## Using the npm package
-
-The `@wailsio/runtime` package is a JavaScript package that provides access to
-the Wails runtime from the frontend. It is used by all standard templates
-and is the recommended way to integrate the runtime into your application.
-By using the `@wailsio/runtime` package, you will only include the parts of the runtime that you use.
-
-The package is available on npm and can be installed using:
-
-```shell
-npm install --save @wailsio/runtime
-```
-
-## Using a pre-built bundle
-
-Some projects will not use a Javascript bundler and may prefer to use a
-pre-built bundled version of the runtime. This version can be generated locally
-using the following command:
-
-```shell
-wails3 generate runtime
-```
-
-The command will output a `runtime.js` (and `runtime.debug.js`) file in the current
-directory. This file is an ES module that can be imported by your application scripts
-just like the npm package, but the API is also exported to the global window object,
-so for simpler applications you can use it as follows:
-
-```html
-
-
-
-
-
-
-
-```
-
-:::caution
-It is important to include the `type="module"` attribute on the `
+
+
+
+