---
title: Application Menus
description: Create native menu bars for your desktop application
sidebar:
  order: 1
---

import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";

## The Problem

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.

## The Wails Solution

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

{/* VISUAL PLACEHOLDER: Menu Bar Comparison
Description: Three screenshots side-by-side showing the same Wails menu on:
1. macOS - Global menu bar at top of screen with app name
2. Windows - Menu bar in window title bar
3. Linux (GNOME) - Menu bar in window
All showing identical menu structure: File, Edit, View, Tools, Help
Style: Clean screenshots with subtle borders, labels indicating platform
*/}

## Quick Start

```go
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.

## Creating Menus

### Basic Menu Creation

```go
// 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()
})
```

### Setting the Menu

**Recommended approach** — Use `UseApplicationMenu` for cross-platform consistency:

```go
// 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:**

<Tabs syncKey="platform">
  <TabItem label="macOS" icon="apple">
    **Global menu bar** (one per application):

    ```go
    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.
  </TabItem>

  <TabItem label="Windows" icon="seti:windows">
    **Per-window menu bar**:

    ```go
    // Option 1: Use application menu (recommended)
    app.Menu.Set(menu)
    window := app.Window.NewWithOptions(application.WebviewWindowOptions{
        UseApplicationMenu: true,
    })

    // Option 2: Set menu directly on window
    window.SetMenu(menu)
    ```

    Each window can have its own menu, or inherit the application menu. The menu appears in the window's title bar.
  </TabItem>

  <TabItem label="Linux" icon="linux">
    **Per-window menu bar** (usually):

    ```go
    // Option 1: Use application menu (recommended)
    app.Menu.Set(menu)
    window := app.Window.NewWithOptions(application.WebviewWindowOptions{
        UseApplicationMenu: true,
    })

    // Option 2: Set menu directly on window
    window.SetMenu(menu)
    ```

    Behaviour varies by desktop environment. Some (like Unity) support global menus.
  </TabItem>
</Tabs>

:::tip[Simplify Cross-Platform Menus]
Using `UseApplicationMenu: true` eliminates the need for platform-specific code like:
```go
// Old approach - no longer needed
if runtime.GOOS == "darwin" {
    app.Menu.Set(menu)
} else {
    window.SetMenu(menu)
}
```
:::

**Per-window custom menus:**

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

```go
window.SetMenu(customMenu)  // Overrides UseApplicationMenu
```

## Menu Roles

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

### Available Roles

| Role | Description | Platform Notes |
|------|-------------|----------------|
| `AppMenu` | Application menu with About, Preferences, Quit | **macOS only** |
| `FileMenu` | File operations (New, Open, Save, etc.) | All platforms |
| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste) | All platforms |
| `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms |
| `HelpMenu` | Help and information | All platforms |

### Using Roles

```go
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:**

<Tabs syncKey="platform">
  <TabItem label="macOS" icon="apple">
    **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
  </TabItem>

  <TabItem label="Windows" icon="seti:windows">
    **FileMenu**:
    - New (Ctrl+N)
    - Open... (Ctrl+O)
    - ---
    - Exit (Alt+F4)

    **EditMenu**:
    - Undo (Ctrl+Z)
    - Redo (Ctrl+Y)
    - ---
    - Cut (Ctrl+X)
    - Copy (Ctrl+C)
    - Paste (Ctrl+V)
    - Select All (Ctrl+A)

    **WindowMenu**:
    - Minimise
    - Maximise

    **HelpMenu**:
    - About [App Name]
  </TabItem>

  <TabItem label="Linux" icon="linux">
    Similar to Windows, but keyboard shortcuts may vary by desktop environment.
  </TabItem>
</Tabs>

### Customising Role Menus

Add items to role menus:

```go
fileMenu := menu.AddRole(application.FileMenu)

// Add custom items
fileMenu.Add("Import...").OnClick(handleImport)
fileMenu.Add("Export...").OnClick(handleExport)
```

## Custom Menus

Create your own menus for application-specific features:

```go
// 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](/features/menus/reference).

## Dynamic Menus

Update menus based on application state:

### Enable/Disable Items

```go
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!
}
```

:::caution[Always Call menu.Update()]
After changing menu state (enable/disable, label, checked), **always call `menu.Update()`**. This is especially critical on Windows where menus are reconstructed.

See [Menu Reference](/features/menus/reference#enabled-state) for details.
:::

### Change Labels

```go
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()
})
```

### Rebuild Menus

For major changes, rebuild the entire menu:

```go
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)
}
```

## Window Control from Menus

Menu items can control windows:

```go
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:**

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

## Platform-Specific Considerations

### macOS

**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:**

```go
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
}
```

### Windows

**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:**

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

### Linux

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

## Complete Example

Here's a production-ready menu structure:

```go
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
}
```

## Best Practices

### ✅ Do

- **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

- **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

## Next Steps

<CardGrid>
  <Card title="Menu Reference" icon="document">
    Complete reference for menu item types and properties.
    
    [Learn More →](/features/menus/reference)
  </Card>

  <Card title="Context Menus" icon="puzzle">
    Create right-click context menus.
    
    [Learn More →](/features/menus/context)
  </Card>

  <Card title="System Tray Menus" icon="star">
    Add system tray/menu bar integration.
    
    [Learn More →](/features/menus/systray)
  </Card>

  <Card title="Menu Patterns" icon="open-book">
    Common menu patterns and best practices.
    
    [Learn More →](/guides/patterns/menus)
  </Card>
</CardGrid>

---

**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).
