Skip to content

Menu API

The Menu API provides methods to create and manage application menus, context menus, and system tray menus.

Menu Types:

  • Application Menus - Top menu bar (File, Edit, etc.)
  • Context Menus - Right-click menus
  • System Tray Menus - Menus in the system tray/notification area

Creates a new menu.

func (a *App) NewMenu() *Menu

Example:

menu := app.NewMenu()

Adds a menu item to the menu.

func (m *Menu) Add(label string) *MenuItem

Parameters:

  • label - The text displayed for the menu item

Returns: The created menu item

Example:

item := menu.Add("Open File")
item.OnClick(func(ctx *application.Context) {
// Handle click
})

Adds a submenu to the menu.

func (m *Menu) AddSubmenu(label string) *Menu

Parameters:

  • label - The submenu label

Returns: The created submenu

Example:

fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New")
fileMenu.Add("Open")
fileMenu.Add("Save")

Adds a visual separator line between menu items.

func (m *Menu) AddSeparator()

Example:

menu.Add("Copy")
menu.Add("Paste")
menu.AddSeparator()
menu.Add("Select All")

Best practice: Use separators to group related menu items.

Adds a checkable menu item.

func (m *Menu) AddCheckbox(label string, checked bool) *MenuItem

Parameters:

  • label - The checkbox label
  • checked - Initial checked state

Example:

darkMode := menu.AddCheckbox("Dark Mode", false)
darkMode.OnClick(func(ctx *application.Context) {
isChecked := darkMode.Checked()
// Toggle dark mode
})

Adds a radio menu item (mutually exclusive group).

func (m *Menu) AddRadio(label string, checked bool) *MenuItem

Parameters:

  • label - The radio button label
  • checked - Initial checked state

Example:

// Create radio group for view modes
viewMenu := menu.AddSubmenu("View")
listView := viewMenu.AddRadio("List View", true)
gridView := viewMenu.AddRadio("Grid View", false)
treeView := viewMenu.AddRadio("Tree View", false)
listView.OnClick(func(ctx *application.Context) {
setViewMode("list")
})
gridView.OnClick(func(ctx *application.Context) {
setViewMode("grid")
})

Updates the menu to reflect any changes made to menu items.

func (m *Menu) Update()

Example:

item.SetEnabled(false)
menu.Update() // Must call to apply changes

Important: Always call Update() after modifying menu item properties.

Registers a click handler for the menu item.

func (mi *MenuItem) OnClick(callback func(ctx *application.Context)) *MenuItem

Parameters:

  • callback - Function called when item is clicked

Returns: The menu item (for chaining)

Example:

item.OnClick(func(ctx *application.Context) {
fmt.Println("Menu item clicked")
app.Logger.Info("User clicked menu item")
})

Changes the menu item’s label.

func (mi *MenuItem) SetLabel(label string) *MenuItem

Example:

item.SetLabel("Save As...")
menu.Update()

Enables or disables the menu item.

func (mi *MenuItem) SetEnabled(enabled bool) *MenuItem

Example:

// Disable save when no document is open
saveItem.SetEnabled(hasOpenDocument)
menu.Update()

Common pattern:

// Update menu state based on application state
func updateMenuState() {
saveItem.SetEnabled(hasUnsavedChanges)
undoItem.SetEnabled(canUndo)
redoItem.SetEnabled(canRedo)
menu.Update()
}

Sets the checked state for checkbox/radio menu items.

func (mi *MenuItem) SetChecked(checked bool) *MenuItem

Example:

darkModeItem.SetChecked(isDarkModeEnabled)
menu.Update()

Returns the current checked state.

func (mi *MenuItem) Checked() bool

Example:

if darkModeItem.Checked() {
// Dark mode is enabled
}

Sets a keyboard shortcut for the menu item.

func (mi *MenuItem) SetAccelerator(accelerator string) *MenuItem

Parameters:

  • accelerator - Keyboard shortcut (e.g., “Ctrl+S”, “Cmd+Q”)

Accelerator format:

  • Modifiers: Ctrl, Cmd, Alt, Shift
  • Keys: A-Z, 0-9, F1-F12, Enter, Backspace, etc.
  • Platform: Use Cmd on macOS, Ctrl on Windows/Linux

Example:

saveItem.SetAccelerator("Ctrl+S")
quitItem.SetAccelerator("Ctrl+Q")
newItem.SetAccelerator("Ctrl+N")

Platform-aware example:

import "runtime"
var quitShortcut string
if runtime.GOOS == "darwin" {
quitShortcut = "Cmd+Q"
} else {
quitShortcut = "Ctrl+Q"
}
quitItem.SetAccelerator(quitShortcut)

Sets a tooltip that appears when hovering over the menu item.

func (mi *MenuItem) SetTooltip(tooltip string) *MenuItem

Example:

item.SetTooltip("Opens a file from disk")

Shows or hides the menu item.

func (mi *MenuItem) SetHidden(hidden bool) *MenuItem

Example:

// Hide debug menu in production
debugItem.SetHidden(!isDevelopment)
menu.Update()

Sets the application’s main menu bar.

func (mm *MenuManager) Set(menu *Menu)

Example:

menu := app.NewMenu()
// File menu
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New").SetAccelerator("Ctrl+N").OnClick(newFile)
fileMenu.Add("Open").SetAccelerator("Ctrl+O").OnClick(openFile)
fileMenu.Add("Save").SetAccelerator("Ctrl+S").OnClick(saveFile)
fileMenu.AddSeparator()
fileMenu.Add("Exit").SetAccelerator("Ctrl+Q").OnClick(func(ctx *application.Context) {
app.Quit()
})
// Edit menu
editMenu := menu.AddSubmenu("Edit")
editMenu.Add("Undo").SetAccelerator("Ctrl+Z").OnClick(undo)
editMenu.Add("Redo").SetAccelerator("Ctrl+Y").OnClick(redo)
editMenu.AddSeparator()
editMenu.Add("Cut").SetAccelerator("Ctrl+X").OnClick(cut)
editMenu.Add("Copy").SetAccelerator("Ctrl+C").OnClick(copy)
editMenu.Add("Paste").SetAccelerator("Ctrl+V").OnClick(paste)
app.Menu.Set(menu)

Platform notes:

  • macOS: Menu appears in the top menu bar
  • Windows/Linux: Menu appears in the window title bar
  • macOS: Automatically adds application menu with app name

Registers a context menu (right-click menu) with a specific name.

func (cm *ContextMenuManager) Add(name string, menu *Menu)

Parameters:

  • name - Unique identifier for the context menu
  • menu - The menu to show

Go:

// Create context menu
contextMenu := app.NewMenu()
contextMenu.Add("Cut").OnClick(cut)
contextMenu.Add("Copy").OnClick(copy)
contextMenu.Add("Paste").OnClick(paste)
contextMenu.AddSeparator()
contextMenu.Add("Select All").OnClick(selectAll)
// Register it
app.RegisterContextMenu("editor", contextMenu)

HTML:

<!-- Trigger context menu on right-click -->
<div data-wails-context-menu="editor">
Right-click here for context menu
</div>

Dynamic context menus:

// Update context menu based on selection
func updateContextMenu() {
contextMenu := app.NewMenu()
if hasSelection {
contextMenu.Add("Cut").OnClick(cut)
contextMenu.Add("Copy").OnClick(copy)
}
contextMenu.Add("Paste").SetEnabled(hasClipboardContent).OnClick(paste)
app.RegisterContextMenu("editor", contextMenu)
}

Creates a new system tray icon.

func (sm *SystemTrayManager) New() *SystemTray

Example:

tray := app.SystemTray.New()

Sets the system tray icon.

func (st *SystemTray) SetIcon(icon []byte) *SystemTray

Example:

iconData, _ := os.ReadFile("icon.png")
tray.SetIcon(iconData)

Sets the menu for the system tray.

func (st *SystemTray) SetMenu(menu *Menu) *SystemTray

Example:

trayMenu := app.NewMenu()
trayMenu.Add("Show Window").OnClick(func(ctx *application.Context) {
window.Show()
window.SetFocus()
})
trayMenu.Add("Settings").OnClick(openSettings)
trayMenu.AddSeparator()
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
tray.SetMenu(trayMenu)

Sets the tooltip shown when hovering over the tray icon.

func (st *SystemTray) SetTooltip(tooltip string) *SystemTray

Example:

tray.SetTooltip("My Application - Running")

Handles left-click on the tray icon.

func (st *SystemTray) OnClick(callback func()) *SystemTray

Example:

tray.OnClick(func() {
if window.IsVisible() {
window.Hide()
} else {
window.Show()
window.SetFocus()
}
})
package main
import (
"github.com/wailsapp/wails/v3/pkg/application"
)
func createMenu(app *application.Application) *application.Menu {
menu := app.NewMenu()
// File menu
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New").
SetAccelerator("Ctrl+N").
OnClick(func(ctx *application.Context) {
// Create new document
})
fileMenu.Add("Open").
SetAccelerator("Ctrl+O").
OnClick(func(ctx *application.Context) {
// Open file dialog
})
fileMenu.Add("Save").
SetAccelerator("Ctrl+S").
OnClick(func(ctx *application.Context) {
// Save document
})
fileMenu.AddSeparator()
fileMenu.Add("Exit").OnClick(func(ctx *application.Context) {
app.Quit()
})
// Edit menu
editMenu := menu.AddSubmenu("Edit")
editMenu.Add("Undo").SetAccelerator("Ctrl+Z")
editMenu.Add("Redo").SetAccelerator("Ctrl+Y")
editMenu.AddSeparator()
editMenu.Add("Cut").SetAccelerator("Ctrl+X")
editMenu.Add("Copy").SetAccelerator("Ctrl+C")
editMenu.Add("Paste").SetAccelerator("Ctrl+V")
// View menu
viewMenu := menu.AddSubmenu("View")
darkMode := viewMenu.AddCheckbox("Dark Mode", false)
darkMode.OnClick(func(ctx *application.Context) {
// Toggle dark mode
isChecked := darkMode.Checked()
app.Logger.Info("Dark mode", "enabled", isChecked)
})
viewMenu.AddSeparator()
viewMenu.AddRadio("List View", true)
viewMenu.AddRadio("Grid View", false)
viewMenu.AddRadio("Detail View", false)
// Help menu
helpMenu := menu.AddSubmenu("Help")
helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) {
// Open docs
})
helpMenu.Add("About").OnClick(func(ctx *application.Context) {
// Show about dialog
})
return menu
}
func main() {
app := application.New(application.Options{
Name: "Menu Demo",
})
menu := createMenu(app)
app.Menu.Set(menu)
window := app.Window.New()
window.Show()
app.Run()
}
func setupSystemTray(app *application.Application, window *application.Window) {
// Create system tray
tray := app.SystemTray.New()
// Set icon
iconData, _ := os.ReadFile("icon.png")
tray.SetIcon(iconData)
tray.SetTooltip("My App - Running")
// Handle left-click on tray icon
tray.OnClick(func() {
if window.IsVisible() {
window.Hide()
} else {
window.Show()
window.SetFocus()
}
})
// Create tray menu
trayMenu := app.NewMenu()
showItem := trayMenu.Add("Show Window")
showItem.OnClick(func(ctx *application.Context) {
window.Show()
window.SetFocus()
})
trayMenu.AddSeparator()
trayMenu.Add("Settings").OnClick(func(ctx *application.Context) {
// Open settings window
})
trayMenu.Add("About").OnClick(func(ctx *application.Context) {
// Show about dialog
})
trayMenu.AddSeparator()
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
tray.SetMenu(trayMenu)
}
type Editor struct {
app *application.Application
menu *application.Menu
undoItem *application.MenuItem
redoItem *application.MenuItem
saveItem *application.MenuItem
undoStack []string
redoStack []string
hasChanges bool
}
func (e *Editor) createMenu() {
e.menu = e.app.NewMenu()
fileMenu := e.menu.AddSubmenu("File")
e.saveItem = fileMenu.Add("Save").SetAccelerator("Ctrl+S")
e.saveItem.OnClick(func(ctx *application.Context) {
e.save()
})
editMenu := e.menu.AddSubmenu("Edit")
e.undoItem = editMenu.Add("Undo").SetAccelerator("Ctrl+Z")
e.undoItem.OnClick(func(ctx *application.Context) {
e.undo()
})
e.redoItem = editMenu.Add("Redo").SetAccelerator("Ctrl+Y")
e.redoItem.OnClick(func(ctx *application.Context) {
e.redo()
})
e.updateMenuState()
e.app.Menu.Set(e.menu)
}
func (e *Editor) updateMenuState() {
// Update menu items based on current state
e.saveItem.SetEnabled(e.hasChanges)
e.undoItem.SetEnabled(len(e.undoStack) > 0)
e.redoItem.SetEnabled(len(e.redoStack) > 0)
e.menu.Update()
}
func (e *Editor) onChange() {
e.hasChanges = true
e.updateMenuState()
}
func (e *Editor) save() {
// Save logic
e.hasChanges = false
e.updateMenuState()
}
  • Use standard accelerators - Follow platform conventions (Ctrl+C for copy, etc.)
  • Call Update() after changes - Menu won’t reflect changes otherwise
  • Group related items - Use separators to organize menu items
  • Disable unavailable actions - Don’t hide, disable with SetEnabled(false)
  • Use clear labels - Be concise and descriptive
  • Follow platform conventions - macOS vs Windows/Linux menu patterns
  • Don’t forget Update() - Most common mistake
  • Don’t nest too deeply - Keep menus 2-3 levels maximum
  • Don’t use ambiguous labels - “Process” vs “Process Document”
  • Don’t overcomplicate - Keep menus simple and focused
  • Don’t mix metaphors - Consistent naming and organization
  • Application menu automatically added with app name
  • Use Cmd instead of Ctrl for accelerators
  • “About”, “Preferences”, and “Quit” in application menu by default
  • No automatic application menu
  • Use Ctrl for accelerators
  • “Exit” typically in File menu