Menus are an essential part of any macOS app. They usually exist to let users pick an option among others and perform an operation. But sometimes, they also exist to turn on or off some state. As developers, we have to visually indicate when something is enabled or not, but how do we manage that in macOS menus? Is it as straightforward as it sounds? This is what we’re just about to explore in this post.
Getting started with a simple menu
For the sake of the demonstration, suppose that we need a menu in both the toolbar and the main menu, where a user can choose between two different backup options; sync on iCloud or export locally. Programmatically we represent these using an enum, and the selection with a state property as shown next:
|
1 2 3 4 5 6 7 |
enum BackupOption: Int, CaseIterable { case iCloud, local } @State private var backupOption: BackupOption = .iCloud |
Let’s begin with the simplest possible case, a menu with buttons that contain no images or any visual indication of the selected backup option:
|
1 2 3 4 5 6 7 8 9 10 |
Menu("Backup Options", systemImage: "externaldrive") { Button("Sync on iCloud") { backupOption = .iCloud } Button("Export Locally") { backupOption = .local } } |
The above menu implementation assigns the correct value to the backupOption property. However, it doesn’t show which option is actually selected.
Showing a conditional indication
So, we’re going to the next step, which is to display a visual hint conditionally, based on the option that is currently set. A checkmark is the most common choice:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Menu("Backup Options", systemImage: "externaldrive") { Button("Sync on iCloud", systemImage: backupOption == .iCloud ? "checkmark" : "") { backupOption = .iCloud } Button("Export Locally", systemImage: backupOption == .local ? "checkmark" : "") { backupOption = .local } } |


This is a perfectly working solution, but even though it works, it has two downsides:
- We cannot add icons next to the button titles, as we occupy the icon position with the selection icon (the checkmark).
- When no checkmark is displayed, Xcode will always complain with the following message because of a missing symbol (an empty-named symbol is not a valid SF Symbol): No symbol named ” found in system symbol set.
Let’s see what else we can do.
Keeping selection indication as part of the title
SwiftUI allows to present an icon as part of the text in a Text view, and this is rendered as a single piece of text. With that in mind, we can create menu items with titles, icons and the selection indication.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Menu("Backup Options", systemImage: "externaldrive") { Button(backupOption == .iCloud ? "Sync on iCloud \(Image(systemName: "checkmark"))” : "Sync on iCloud", systemImage: "icloud") { // … } Button(backupOption == .local ? "Export Locally \(Image(systemName: "checkmark"))” : "Export Locally", systemImage: "externaldrive.badge.plus") { // … } } |

This approach has the following disadvantages:
- It’s not something that users are familiar with. Apps usually have the selection indication (checkmark) in front of both the text and the icon.
- It works on macOS toolbar, but not quite properly on the main menu. There, it does not maintain the label tint color, and there’s no way to change it:

I would not recommend using this solution unless it’s absolutely necessary, but since it’s something we can do, it deserves to be mentioned.
Using a picker
Menus can contain more than buttons, and the Picker view is a commonly preferred control. A picker can be displayed in various forms, with the menu style being among them. By default, it presents a set of mutually exclusive choices as menu items, where each choice is identified by a tag value. The system automatically indicates the selected item, and that aligns to what users are already familiar with in Apple’s, and not only, apps.
Since a Picker view with the menu style is a menu on its own, it appears as a submenu when used in other menus. For example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Menu("Backup Options", systemImage: "externaldrive") { Picker( "Backup Options", systemImage: "externaldrive", selection: $backupOption ) { Label("Sync on iCloud", systemImage: "icloud") .tag(BackupOption.iCloud) Label("Export Locally", systemImage: "externaldrive.badge.plus") .tag(BackupOption.local) } } |
For simplicity, I used the same title and icon in both the menu and the picker.

If a submenu with selectable options is a desired appearance, then the above implementation is just fine. On the other hand, given that the Picker shows a menu, we can use it without embedding it into another menu:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Picker( "Backup Options", systemImage: "externaldrive", selection: $backupOption ) { Label("Sync on iCloud", systemImage: "icloud") .tag(BackupOption.iCloud) Label("Export Locally", systemImage: "externaldrive.badge.plus") .tag(BackupOption.local) } |

Selected option is shown right in front of the icon and the item title. The picker appears on the toolbar usually with the icon of the selected item, but as you can see next, the issue here is the large width of the picker:

A workaround that usually works and fixes this is to set an explicit width using the .frame modifier:
|
1 2 3 4 5 6 7 8 9 10 |
Picker( "Backup Options", systemImage: "externaldrive", selection: $backupOption ) { // … } .frame(width: 50) |

Picker on a toolbar is a working solution, even though I personally do not like that much the way the menu appears; it’s not a drop-down menu, rather a popup menu.
When it comes to main menu, Picker is displayed as a submenu, even if it’s a top-level item in the menu:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CommandMenu("Backup Picker") { Picker( "Backup Options", systemImage: "externaldrive", selection: $backupOption ) { Label("Sync on iCloud", systemImage: "icloud") .tag(BackupOption.iCloud) Label("Export Locally", systemImage: "externaldrive.badge.plus") .tag(BackupOption.local) } } |

In any case, using a picker to present the selected option with a system-provided visual indication is always a good approach, as long as having it as a submenu is an acceptable presentation type.
Using Toggles
Another control that we don’t easily think of, is the Toggle view. When using a Toggle into a menu, its on state appears with the system-provided checkmark automatically. It’s the perfect choice when presenting options that we need to set on and off and they are backed by Bool properties.
For instance, the following Toggle that’s presented as a menu item toggles the value of the isSyncEnabled state property:
|
1 2 3 4 5 6 7 8 9 10 11 |
@State private var isSyncEnabled = true Menu("Backup Options", systemImage: "externaldrive") { Toggle( "Enable Sync", systemImage: "arrow.trianglehead.2.clockwise.rotate.90", isOn: $isSyncEnabled ) } |

Toggle is the right tool to use to create selectable menu items that alter boolean values between true and false states. However, things become complicated when having mutually excluded values, just like the example we’ve seen in all previous parts with the iCloud syncing and local export. How can we use Toggle views in order to set the correct value to backupOption state property, given that the Toggle expects for the binding value of a Bool property?
The solution here is to create custom binding values that we’ll provide to Toggles, handling manually the getters and setters, applying at the same time the logic we need every time. For instance, see the iCloudOption right next:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private var iCloudOption: Binding<Bool> { Binding( get: { backupOption == .iCloud }, set: { newValue in if newValue == true { backupOption = .iCloud } else { backupOption = .local } } ) } |
We can replace the above with the following shorter version:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private var iCloudOption: Binding<Bool> { Binding { backupOption == .iCloud } set: { newValue in if newValue == true { backupOption = .iCloud } else { backupOption = .local } } } |
iCloudOption is a computed property that returns a Binding<Bool> value; right what the toggle needs. In its closure, we initialize a Binding value and we return true if backupOption has the iCloud value, or false if not. In the setter, when the newValue is true, then the backupOption gets the iCloud value, otherwise it gets the local value.
Note: The logic is not important here. But for mutually excluded values, and depending on the case, Toggles might not be the best choice to make. Yet, it’s well worth it going through this approach too.
In the exact same fashion, let’s add the localOption:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private var localOption: Binding<Bool> { Binding { backupOption == .local } set: { newValue in if newValue == true { backupOption = .local } else { backupOption = .iCloud } } } |
Now we can create a menu with two Toggle views, passing the custom computed properties as arguments to the isOn parameters:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Menu("Backup Options", systemImage: "externaldrive") { Toggle( "Sync on iCloud", systemImage: "icloud", isOn: iCloudOption ) Toggle( "Export Locally", systemImage: "externaldrive.badge.plus", isOn: localOption ) } |
Notice that we don’t need to pass the values with the $ prefix; that would be wrong, as both iCloudOption and localOption are Bool binding values already.

Wrapping up
Obviously, there are quite a few options to present selectable menu items on macOS apps, and we’ve gone through them in this post. Starting with plain buttons, we ended up to pickers and toggles inside menus. Toggle especially is a kind of view that doesn’t easily come to mind, but it can often be proved as a powerful way to show items with a title, icon, and the system selection indicator. I hope you found all that valuable. Thanks for reading!