gui/docs/ref/wails-v3/features/menus/reference.mdx
Snider 4bdbb68f46
Some checks failed
Security Scan / security (push) Failing after 9s
Test / test (push) Failing after 1m21s
refactor: update import path from go-config to core/config
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-14 10:26:36 +00:00

562 lines
12 KiB
Text

---
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).