Skip to content

Application Menus

Professional desktop applications need menu bars—File, Edit, View, Help. But menus work differently on each platform:

  • macOS: Global menu bar at top of screen
  • Windows: Menu bar in window title bar
  • Linux: Varies by desktop environment

Building platform-appropriate menus manually is tedious and error-prone.

Wails provides a unified API that creates platform-native menus automatically. Write once, get native behaviour on all platforms.

package main
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "My App",
})
// Create menu
menu := app.NewMenu()
// Add standard menus (platform-appropriate)
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu) // macOS only
}
menu.AddRole(application.FileMenu)
menu.AddRole(application.EditMenu)
menu.AddRole(application.WindowMenu)
menu.AddRole(application.HelpMenu)
// Set the application menu
app.Menu.Set(menu)
// Create window with UseApplicationMenu to inherit the menu on Windows/Linux
app.Window.NewWithOptions(application.WebviewWindowOptions{
UseApplicationMenu: true,
})
app.Run()
}

That’s it! You now have platform-native menus with standard items. The UseApplicationMenu option ensures Windows and Linux windows display the menu without additional code.

// Create a new menu
menu := app.NewMenu()
// Add a top-level menu
fileMenu := menu.AddSubmenu("File")
// Add menu items
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
// Handle New
})
fileMenu.Add("Open").OnClick(func(ctx *application.Context) {
// Handle Open
})
fileMenu.AddSeparator()
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})

Recommended approach — Use UseApplicationMenu for cross-platform consistency:

// Set the application menu once
app.Menu.Set(menu)
// Create windows that inherit the menu on Windows/Linux
app.Window.NewWithOptions(application.WebviewWindowOptions{
UseApplicationMenu: true, // Window uses the app menu
})

This approach:

  • On macOS: The menu appears at the top of screen (standard behaviour)
  • On Windows/Linux: Each window with UseApplicationMenu: true displays the app menu

Platform-specific details:

Global menu bar (one per application):

app.Menu.Set(menu)

The menu appears at the top of the screen and persists even when all windows are closed. The UseApplicationMenu option has no effect on macOS since all apps use the global menu.

Per-window custom menus:

If a window needs a different menu than the application menu, set it directly:

window.SetMenu(customMenu) // Overrides UseApplicationMenu

Wails provides predefined menu roles that create platform-appropriate menu structures automatically.

RoleDescriptionPlatform Notes
AppMenuApplication menu with About, Preferences, QuitmacOS only
FileMenuFile operations (New, Open, Save, etc.)All platforms
EditMenuText editing (Undo, Redo, Cut, Copy, Paste)All platforms
WindowMenuWindow management (Minimise, Zoom, etc.)All platforms
HelpMenuHelp and informationAll platforms
menu := app.NewMenu()
// macOS: Add application menu
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// All platforms: Add standard menus
menu.AddRole(application.FileMenu)
menu.AddRole(application.EditMenu)
menu.AddRole(application.WindowMenu)
menu.AddRole(application.HelpMenu)

What you get:

AppMenu (with app name):

  • About [App Name]
  • Preferences… (⌘,)

  • Services

  • Hide [App Name] (⌘H)
  • Hide Others (⌥⌘H)
  • Show All

  • Quit [App Name] (⌘Q)

FileMenu:

  • New (⌘N)
  • Open… (⌘O)

  • Close Window (⌘W)

EditMenu:

  • Undo (⌘Z)
  • Redo (⇧⌘Z)

  • Cut (⌘X)
  • Copy (⌘C)
  • Paste (⌘V)
  • Select All (⌘A)

WindowMenu:

  • Minimise (⌘M)
  • Zoom

  • Bring All to Front

HelpMenu:

  • [App Name] Help

Add items to role menus:

fileMenu := menu.AddRole(application.FileMenu)
// Add custom items
fileMenu.Add("Import...").OnClick(handleImport)
fileMenu.Add("Export...").OnClick(handleExport)

Create your own menus for application-specific features:

// Add a custom top-level menu
toolsMenu := menu.AddSubmenu("Tools")
// Add items
toolsMenu.Add("Settings").OnClick(func(ctx *application.Context) {
showSettingsWindow()
})
toolsMenu.AddSeparator()
// Add checkbox
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) {
isDark := ctx.ClickedMenuItem().Checked()
setTheme(isDark)
})
// Add radio group
toolsMenu.AddRadio("Small", true).OnClick(handleFontSize)
toolsMenu.AddRadio("Medium", false).OnClick(handleFontSize)
toolsMenu.AddRadio("Large", false).OnClick(handleFontSize)
// Add submenu
advancedMenu := toolsMenu.AddSubmenu("Advanced")
advancedMenu.Add("Configure...").OnClick(showAdvancedSettings)

For more menu item types, see Menu Reference.

Update menus based on application state:

var saveMenuItem *application.MenuItem
func createMenu() {
menu := app.NewMenu()
fileMenu := menu.AddSubmenu("File")
saveMenuItem = fileMenu.Add("Save")
saveMenuItem.SetEnabled(false) // Initially disabled
saveMenuItem.OnClick(handleSave)
app.SetMenu(menu)
}
func onDocumentChanged() {
saveMenuItem.SetEnabled(hasUnsavedChanges())
menu.Update() // Important!
}
updateMenuItem := menu.Add("Check for Updates")
updateMenuItem.OnClick(func(ctx *application.Context) {
updateMenuItem.SetLabel("Checking...")
menu.Update()
checkForUpdates()
updateMenuItem.SetLabel("Check for Updates")
menu.Update()
})

For major changes, rebuild the entire menu:

func rebuildFileMenu() {
menu := app.NewMenu()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New").OnClick(handleNew)
fileMenu.Add("Open").OnClick(handleOpen)
// Add recent files dynamically
if hasRecentFiles() {
recentMenu := fileMenu.AddSubmenu("Open Recent")
for _, file := range getRecentFiles() {
filePath := file // Capture for closure
recentMenu.Add(filepath.Base(file)).OnClick(func(ctx *application.Context) {
openFile(filePath)
})
}
recentMenu.AddSeparator()
recentMenu.Add("Clear Recent").OnClick(clearRecentFiles)
}
fileMenu.AddSeparator()
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
app.SetMenu(menu)
}

Menu items can control windows:

viewMenu := menu.AddSubmenu("View")
// Toggle fullscreen
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
window := app.GetWindowByName("main")
window.SetFullscreen(!window.IsFullscreen())
})
// Zoom controls
viewMenu.Add("Zoom In").SetAccelerator("CmdOrCtrl++").OnClick(func(ctx *application.Context) {
// Increase zoom
})
viewMenu.Add("Zoom Out").SetAccelerator("CmdOrCtrl+-").OnClick(func(ctx *application.Context) {
// Decrease zoom
})
viewMenu.Add("Reset Zoom").SetAccelerator("CmdOrCtrl+0").OnClick(func(ctx *application.Context) {
// Reset zoom
})

Get the active window:

menuItem.OnClick(func(ctx *application.Context) {
window := application.ContextWindow(ctx)
// Use window
})

Menu bar behaviour:

  • Appears at top of screen (global)
  • Persists when all windows closed
  • First menu is always the application menu
  • Use menu.AddRole(application.AppMenu) for standard items

Standard locations:

  • About: Application menu
  • Preferences: Application menu (⌘,)
  • Quit: Application menu (⌘Q)
  • Help: Help menu

Example:

if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu) // Adds About, Preferences, Quit
// Don't add Quit to File menu on macOS
// Don't add About to Help menu on macOS
}

Menu bar behaviour:

  • Appears in window title bar
  • Each window has its own menu
  • No application menu

Standard locations:

  • Exit: File menu (Alt+F4)
  • Settings: Tools or Edit menu
  • About: Help menu

Example:

if runtime.GOOS == "windows" {
fileMenu := menu.AddRole(application.FileMenu)
// Exit is added automatically
helpMenu := menu.AddRole(application.HelpMenu)
// About is added automatically
}

Menu bar behaviour:

  • Usually per-window (like Windows)
  • Some DEs support global menus (Unity, GNOME with extension)
  • Appearance varies by desktop environment

Best practice: Follow Windows conventions, test on target DEs.

Here’s a production-ready menu structure:

package main
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "My Application",
})
// Create and set menu
createMenu(app)
// Create main window with UseApplicationMenu for cross-platform menu support
app.Window.NewWithOptions(application.WebviewWindowOptions{
UseApplicationMenu: true,
})
app.Run()
}
func createMenu(app *application.Application) {
menu := app.NewMenu()
// Platform-specific application menu (macOS only)
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// File menu
fileMenu := menu.AddRole(application.FileMenu)
fileMenu.Add("Import...").SetAccelerator("CmdOrCtrl+I").OnClick(handleImport)
fileMenu.Add("Export...").SetAccelerator("CmdOrCtrl+E").OnClick(handleExport)
// Edit menu
menu.AddRole(application.EditMenu)
// View menu
viewMenu := menu.AddSubmenu("View")
viewMenu.Add("Toggle Fullscreen").SetAccelerator("F11").OnClick(toggleFullscreen)
viewMenu.AddSeparator()
viewMenu.AddCheckbox("Show Sidebar", true).OnClick(toggleSidebar)
viewMenu.AddCheckbox("Show Toolbar", true).OnClick(toggleToolbar)
// Tools menu
toolsMenu := menu.AddSubmenu("Tools")
// Settings location varies by platform
if runtime.GOOS == "darwin" {
// On macOS, Preferences is in Application menu (added by AppMenu role)
} else {
toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings)
}
toolsMenu.AddSeparator()
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(toggleDarkMode)
// Window menu
menu.AddRole(application.WindowMenu)
// Help menu
helpMenu := menu.AddRole(application.HelpMenu)
helpMenu.Add("Documentation").OnClick(openDocumentation)
// About location varies by platform
if runtime.GOOS == "darwin" {
// On macOS, About is in Application menu (added by AppMenu role)
} else {
helpMenu.AddSeparator()
helpMenu.Add("About").OnClick(showAbout)
}
// Set the application menu
app.Menu.Set(menu)
}
func handleImport(ctx *application.Context) {
// Implementation
}
func handleExport(ctx *application.Context) {
// Implementation
}
func toggleFullscreen(ctx *application.Context) {
window := application.ContextWindow(ctx)
window.SetFullscreen(!window.IsFullscreen())
}
func toggleSidebar(ctx *application.Context) {
// Implementation
}
func toggleToolbar(ctx *application.Context) {
// Implementation
}
func showSettings(ctx *application.Context) {
// Implementation
}
func toggleDarkMode(ctx *application.Context) {
isDark := ctx.ClickedMenuItem().Checked()
// Apply theme
}
func openDocumentation(ctx *application.Context) {
// Open browser
}
func showAbout(ctx *application.Context) {
// Show about dialog
}
  • Use menu roles for standard menus (File, Edit, etc.)
  • Follow platform conventions for menu structure
  • Add keyboard shortcuts to common actions
  • Call menu.Update() after changing menu state
  • Test on all platforms - behaviour varies
  • Keep menus shallow - 2-3 levels maximum
  • Use clear labels - “Save Project” not “Save”
  • Don’t hardcode platform shortcuts - Use CmdOrCtrl
  • Don’t put Quit in File menu on macOS - It’s in Application menu
  • Don’t put About in Help menu on macOS - It’s in Application menu
  • Don’t forget menu.Update() - Menus won’t work properly
  • Don’t nest too deeply - Users get lost
  • Don’t use jargon - Keep labels user-friendly

Menu Reference

Complete reference for menu item types and properties.

Learn More →

System Tray Menus

Add system tray/menu bar integration.

Learn More →

Menu Patterns

Common menu patterns and best practices.

Learn More →


Questions? Ask in Discord or check the menu example.