Skip to content

How to use Menu in SwiftUI | SwiftUI Bootcamp #68

Menu gives you the native iOS contextual menu — the same style used in Share Sheet buttons and three-dot menus across Apple's own apps — with just a few lines of code. It's the right choice when you have several actions to offer but don't want to clutter the screen with multiple buttons.

What You'll Learn

  • How to create a Menu with a title and multiple button actions
  • How Menu renders differently on iPhone (context menu popup) vs iPad (popover)
  • How to nest menus and add submenus for organizing related actions

Mental Model

A Menu is like a three-dot "more" button that reveals a context menu. You tap it, a list of actions floats up from the button, you pick one, and it dismisses. This is different from a Picker (which is for selecting a persistent value) and different from a Sheet (which presents a full view). A Menu is for triggering one-off actions from a compact trigger control.

The key design principle: a Menu is about actions, not state. If you need to select a value that persists (like "sort order"), use Picker. If you need to trigger one-time operations (like "duplicate", "delete", "share"), use Menu.

Detailed Explanation

Menu accepts a label (which can be a Text, an Image, or any view) and a content closure containing the actions to show. The content closure supports Button, nested Menu, Divider, Toggle, and Picker views. SwiftUI renders the menu's label as the tappable trigger and presents the content as a floating panel.

The label follows the same rules as other SwiftUI containers: you can use a plain string Menu("Click me!") which creates a text button, or Menu(content:label:) for a fully custom label. For toolbar placements, using Label("Edit", systemImage: "pencil") as the label gives you an icon that automatically respects toolbar styling.

Nested Menu views create submenus — the sample's code shows this pattern. A submenu's trigger appears inline in the parent menu with a trailing chevron, and tapping it reveals the submenu. Keep submenus shallow (one level is usually enough) — deeply nested menus become hard to navigate.

On iPhone, Menu renders as a contextual popup anchored to the button. On iPad, it renders as a popover. Menu is available from iOS 14+.

Code Structure

MenuBootcamp.swift shows the most minimal possible Menu: a labeled trigger and four Button items inside. The empty button closures invite you to fill in real actions — copy text, delete a record, share a link. This spartan example makes it easy to adapt to any real-world scenario.

Complete Code

swift
import SwiftUI

struct MenuBootcamp: View {
    var body: some View {
        Menu("Click me!") { // The string becomes the label/trigger button for the menu
            Button("One") {
                // Action for "One" — e.g., copy, share, or any one-off operation
            }
            Button("Two") {
                // Action for "Two"
            }
            Button("Three") {
                // Action for "Three"
            }
            Button("Four") {
                // Action for "Four"
            }
        }
    }
}

struct MenuBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        MenuBootcamp()
    }
}

Code Walkthrough

  1. Menu("Click me!") — The string initializer creates a Text label as the trigger button. SwiftUI styles this as a tappable element. Long-press (or sometimes single tap) on the label presents the menu content. On iOS 15+, a single tap always presents it — long press was only required on earlier versions.

  2. Content closure — Everything inside the trailing closure becomes a menu item. SwiftUI renders them in order, top to bottom. The last item is always at the bottom, which is often where "destructive" actions like "Delete" are placed (paired with .buttonStyle(.destructive)).

  3. Button("One") { } through Button("Four") { } — Each button is a separate action. The empty closures here are placeholders — in a real app, these closures would call a function on your view model, trigger a share sheet, or modify some state. The buttons appear in the menu in declaration order.

  4. No @State needed — Notice there's no state in this view. A Menu doesn't need to track any state by itself — the actions inside its buttons are what create side effects. This is why Menu is suitable for action-oriented scenarios rather than selection scenarios.

Common Mistakes

Mistake: Using Menu when you should use Picker
If you're selecting a value that should persist (like sorting order or a filter), use Picker with .menu style: Picker("Sort", selection: $sort) { ... }.pickerStyle(.menu). This correctly models the "pick one option that stays selected" pattern. A Menu with Button items is for one-time actions, not persistent selection.

Mistake: Building a deeply nested submenu
Users find submenus harder to navigate than a flat list. If you need more than 6–8 items, consider using a Form or List in a sheet instead of a nested menu hierarchy. Apple's HIG recommends menus be short enough to see all items without scrolling.

Mistake: Adding a Menu to a toolbar with a Button wrapper unnecessarily
In .toolbar, you can place a Menu directly in a ToolbarItem without wrapping it in a Button. Adding an extra Button wrapper changes the hit area and can create visual artifacts. Menu already renders as a button-like control on its own.

Key Takeaways

  • Menu is for groups of one-off actions; use Picker with .pickerStyle(.menu) when selecting a persistent value
  • The content closure can include Button, nested Menu, Divider, Toggle, and Picker — giving you a rich palette for complex menus
  • Menu is available on iOS 14+ and adapts automatically between iPhone context menus and iPad popovers

Last updated: June 27, 2026

Released under the MIT License.