---
title: Menu API
description: Complete reference for the Menu API
sidebar:
  order: 3
---

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

## Overview

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

## Creating Menus

### NewMenu()

Creates a new menu.

```go
func (a *App) NewMenu() *Menu
```

**Example:**
```go
menu := app.NewMenu()
```

## Menu Methods

### Add()

Adds a menu item to the menu.

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

**Parameters:**
- `label` - The text displayed for the menu item

**Returns:** The created menu item

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

### AddSubmenu()

Adds a submenu to the menu.

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

**Parameters:**
- `label` - The submenu label

**Returns:** The created submenu

**Example:**
```go
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New")
fileMenu.Add("Open")
fileMenu.Add("Save")
```

### AddSeparator()

Adds a visual separator line between menu items.

```go
func (m *Menu) AddSeparator()
```

**Example:**
```go
menu.Add("Copy")
menu.Add("Paste")
menu.AddSeparator()
menu.Add("Select All")
```

**Best practice:** Use separators to group related menu items.

### AddCheckbox()

Adds a checkable menu item.

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

**Parameters:**
- `label` - The checkbox label
- `checked` - Initial checked state

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

### AddRadio()

Adds a radio menu item (mutually exclusive group).

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

**Parameters:**
- `label` - The radio button label
- `checked` - Initial checked state

**Example:**
```go
// 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")
})
```

### Update()

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

```go
func (m *Menu) Update()
```

**Example:**
```go
item.SetEnabled(false)
menu.Update()  // Must call to apply changes
```

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

## Menu Item Methods

### OnClick()

Registers a click handler for the menu item.

```go
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:**
```go
item.OnClick(func(ctx *application.Context) {
    fmt.Println("Menu item clicked")
    app.Logger.Info("User clicked menu item")
})
```

### SetLabel()

Changes the menu item's label.

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

**Example:**
```go
item.SetLabel("Save As...")
menu.Update()
```

### SetEnabled()

Enables or disables the menu item.

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

**Example:**
```go
// Disable save when no document is open
saveItem.SetEnabled(hasOpenDocument)
menu.Update()
```

**Common pattern:**
```go
// Update menu state based on application state
func updateMenuState() {
    saveItem.SetEnabled(hasUnsavedChanges)
    undoItem.SetEnabled(canUndo)
    redoItem.SetEnabled(canRedo)
    menu.Update()
}
```

### SetChecked()

Sets the checked state for checkbox/radio menu items.

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

**Example:**
```go
darkModeItem.SetChecked(isDarkModeEnabled)
menu.Update()
```

### Checked()

Returns the current checked state.

```go
func (mi *MenuItem) Checked() bool
```

**Example:**
```go
if darkModeItem.Checked() {
    // Dark mode is enabled
}
```

### SetAccelerator()

Sets a keyboard shortcut for the menu item.

```go
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:**
```go
saveItem.SetAccelerator("Ctrl+S")
quitItem.SetAccelerator("Ctrl+Q")
newItem.SetAccelerator("Ctrl+N")
```

**Platform-aware example:**
```go
import "runtime"

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

### SetTooltip()

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

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

**Example:**
```go
item.SetTooltip("Opens a file from disk")
```

### SetHidden()

Shows or hides the menu item.

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

**Example:**
```go
// Hide debug menu in production
debugItem.SetHidden(!isDevelopment)
menu.Update()
```

## Application Menu

### app.Menu.Set()

Sets the application's main menu bar.

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

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

## Context Menus

### app.ContextMenu.Add()

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

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

**Parameters:**
- `name` - Unique identifier for the context menu
- `menu` - The menu to show

**Go:**

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

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

**Dynamic context menus:**
```go
// 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)
}
```

## System Tray Menu

### app.SystemTray.New()

Creates a new system tray icon.

```go
func (sm *SystemTrayManager) New() *SystemTray
```

**Example:**
```go
tray := app.SystemTray.New()
```

### SetIcon()

Sets the system tray icon.

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

**Example:**
```go
iconData, _ := os.ReadFile("icon.png")
tray.SetIcon(iconData)
```

### SetMenu()

Sets the menu for the system tray.

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

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

### SetTooltip()

Sets the tooltip shown when hovering over the tray icon.

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

**Example:**
```go
tray.SetTooltip("My Application - Running")
```

### OnClick()

Handles left-click on the tray icon.

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

**Example:**
```go
tray.OnClick(func() {
    if window.IsVisible() {
        window.Hide()
    } else {
        window.Show()
        window.SetFocus()
    }
})
```

## Complete Examples

### Standard Application Menu

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

### System Tray Application

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

### Dynamic Menu Updates

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

## Best Practices

### ✅ Do

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

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

## Platform-Specific Notes

### macOS

- Application menu automatically added with app name
- Use `Cmd` instead of `Ctrl` for accelerators
- "About", "Preferences", and "Quit" in application menu by default

### Windows/Linux

- No automatic application menu
- Use `Ctrl` for accelerators
- "Exit" typically in File menu
