# How to add keyboard shortcuts to switch tabs in your iPadOS or macOS app

Recently, I added keyboard shortcuts to my time-tracking app [Moons](https://apps.apple.com/app/id1632174045) allowing users to switch tabs by pressing **⌘1**, **⌘2**, **⌘3** and **⌘4**. I also added corresponding commands to the app’s menu. Here’s a comparison of the View menu in my app BEFORE and AFTER these changes:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751456973229/47776331-cf54-4de1-a5f5-05db91c53518.jpeg align="center")

## Tabs

I’d like to share the code so anyone can add such shortcuts to their apps. It’s fairly simple. I needed to use two modifiers for this: [commands](https://developer.apple.com/documentation/swiftui/scene/commands\(content:\)) and [keyboardShortcut](https://developer.apple.com/documentation/swiftui/view/keyboardshortcut\(_:modifiers:\)). I’ll come back to these, but let’s start from the beginning. To switch tabs, we need a `TabView` first. `TabView` in my ContentView looks like this:

```swift
TabView(selection: $appState.selectedTab) {
	Tab("Timer", systemImage: "timer.circle.fill", value: .timer) {
		TimerView()
	}
	
	Tab("Projects", image: "moons.project.done", value: .projects) {
		ProjectsView()
	}
	
	Tab("Time Entries", systemImage: "list.bullet.circle.fill", value: .timeEntries) {
		TimeEntriesView()
	}
	
	Tab("More", systemImage: "ellipsis.circle.fill", value: .more) {
		MoreView()
	}
}
```

As you can see, I’m using `$appState.selectedTab` as the selection. This `appState` is an `EnvironmentObject` of a custom type `AppState` - an ObservableObject I defined in another file. It looks like this:

```swift
final class AppState: ObservableObject {
	enum Tab: Hashable {
		case timer, projects, timeEntries, more
	}

	@Published var selectedTab: Tab? = .timer
//	...
//	Some other properties I need for other shortcuts.
}
```

In the `AppState` class, I have an enum called `Tab` and a Published var `selectedTab` of type `Tab`.

Basically, every time user switches a tab in the app (by tapping or clicking a tab), the value of `selectedTab` changes: .timer for TimerView(), .projects for ProjectsView() and so on. You can see this value in the `Tab` part of the code I showed you earlier:

```swift
Tab("Timer", systemImage: "timer.circle.fill", value: .timer) { // <- here
	TimerView()
}
```

## Commands

To enable tab switching not only by tapping or clicking on the tabs, but also through the app’s menu, I needed to add the `commands` modifier to the WindowGroup in the Scene of the App struct.

```swift
var body: some Scene {
	WindowGroup {
		ContentView()
			.environmentObject(appState)
	}
	.commands {
		CommandGroup(before: .sidebar) {
			Divider()
			
			Button("Timer") {
				appState.selectedTab = .timer
			}
			
			Button("Projects") {
				appState.selectedTab = .projects
			}
			
			Button("Time Entries") {
				appState.selectedTab = .timeEntries
			}
			
			Button("More") {
				appState.selectedTab = .more
			}
			
			Divider()
		}
	}
}
```

What is happening here… In the `commands` modifier, I’ve added a `CommandGroup`, which is a group of controls that appear in the app’s menu. Notice the `(before: .sidebar)` part - it tells SwiftUI to place my custom commands before the **Show/Hide Sidebar** and **Enter/Exit Full Screen**.

This group contains four buttons, with a divider above and below them. Each button updates the value of `appState.selectedTab`.

Here’s a comparison of my app BEFORE and AFTER adding this code:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751459951713/78ae7e52-d26e-4faa-ab02-aaab5dd31f1e.jpeg align="center")

As you may have noticed, the new commands now appear in the View menu (before the Hide Sidebar and Enter Full Screen, as specified). However, there are no keyboard shortcuts yet.

## Keyboard Shortcuts

To assign keyboard shortcuts to commands, we need to add the `keyboardShortcut` modifier to each button in the `CommandGroup`. Like this:

```swift
.commands {
	CommandGroup(before: .sidebar) {
		Divider()
		
		Button("Timer") {
			appState.selectedTab = .timer
		}
		.keyboardShortcut("1", modifiers: [.command])
		
		Button("Projects") {
			appState.selectedTab = .projects
		}
		.keyboardShortcut("2", modifiers: [.command])
		
		Button("Time Entries") {
			appState.selectedTab = .timeEntries
		}
		.keyboardShortcut("3", modifiers: [.command])
		
		Button("More") {
			appState.selectedTab = .more
		}
		.keyboardShortcut("4", modifiers: [.command])
		
		Divider()
	}
}
```

Since the **⌘Command** key is the default modifier for keyboard shortcuts, we can also write it like this:

```swift
.keyboardShortcut("1")

// Instead of this:
// .keyboardShortcut("1", modifiers: [.command])
```

With the `keyboardShortcut` modifiers, my commands now have shortcuts assigned to them:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751456973229/47776331-cf54-4de1-a5f5-05db91c53518.jpeg align="center")

## Alternative code (Optional)

Your code may be simpler than mine. I’ve created my ObservableObject `AppState` for other reasons. For example, I’ve added keyboard shortcuts to create a New Project or New Task with ⌘N and ⌘T, and I have some conditional logic as well.

However, if your app is simpler and all you need are keyboard shortcuts to switch tabs, you can just use a State variable along with a `Tab` enum:

```swift
enum Tab: Hashable {
	case timer, projects, timeEntries, more
}
```

Since we want to switch tabs using commands attached to the `WindowGroup`, we need to have a `selectedTab` variable in the `App` struct:

```swift
@State private var selectedTab: Tab? = .timer
```

The `commands` in the `App` would use this State variable and look like this:

```swift
.commands {
	CommandGroup(before: .sidebar) {
		Divider()
		
		Button("Timer") {
			selectedTab = .timer
		}
		.keyboardShortcut("1")
		
		Button("Projects") {
			selectedTab = .projects
		}
		.keyboardShortcut("2")
		
		Button("Time Entries") {
			selectedTab = .timeEntries
		}
		.keyboardShortcut("3")
		
		Button("More") {
			selectedTab = .more
		}
		.keyboardShortcut("4")
		
		Divider()
	}
}
```

In `ContentView`, we need to have a binding to the `selectedTab` variable:

```swift
@Binding var selectedTab: Tab?
```

And the `Tabview` would look like this:

```swift
TabView(selection: $selectedTab) {
    Tab("Timer", systemImage: "timer.circle.fill", value: .timer) {
		TimerView()
	}

    // Other tabs
}
```

So basically, we would use `$selectedTab` as the `selection`, not `$appState.selectedTab`.

And you would need to pass `selectedTab` value to `ContentView` in your `WindowGroup`, like this:

```swift
WindowGroup {
    ContentView(selectedTab: $selectedTab)
}
```

And that’s it! Now you know how to add keyboard shortcuts and commands to switch tabs in your iPadOS or macOS apps 🙂

## Thank you for reading!

If you want to support my work, please like, comment or share the article.  
And most importantly...

📱Check out my apps on the App Store:  
[https://apps.apple.com/developer/next-planet/id1495155532](https://apps.apple.com/developer/next-planet/id1495155532)

☕ If you like what I do, consider supporting me on Ko-fi! Every little bit means the world!  
[https://ko-fi.com/kslazinski](https://ko-fi.com/kslazinski)
