App Switcher for MacOS: Switch between Windows from different apps.
Swift
2
16 commits
updated Oct 6, 2026
A tiny macOS menu-bar app that switches between windows, not apps.
Cmd+Tab activates a whole app, so every window of that app jumps forward and buries what you were looking at. Option+Tab brings back just the one window you were in before. Hold Option to pick any window from a list that you can pin, number and filter.
macOS 13 or later · Apple Silicon and Intel · install steps · all releases

AltTab.app onto the Applications shortcut.xattr -dr com.apple.quarantine /Applications/AltTab.app
AltTab ⚠︎ until the permission is active, then just AltTab.Turn on Launch at Login from the menu-bar menu or Preferences.
| Filter | Larger font | Cmd+Tab app list |
|---|---|---|
![]() | ![]() | ![]() |
![]()

| Keys | Action |
|---|---|
| Option+Tab (tap) | switch to the previous window |
| Option held + Tab | open the list; release Option to switch |
| Cmd+Tab (if enabled in Preferences) | the same, listing windows (default) or running apps, as chosen in Preferences |
| Option+Tab with Cmd+Tab listing windows | the same, listing running apps instead (Option+Tab keeps listing windows when Cmd+Tab lists apps) |
While the list is open:
| Keys / mouse | Action |
|---|---|
| Tab / → / ↓ · Shift+Tab / ← / ↑ | move the selection |
| 1–9, 0 | jump straight to that row |
| Shift+1–9, 0, the app's own key (e.g. C) or click an icon in the top bar | open that Quick Launch app |
| Shift+Tab / ↑ on the first row | move to the filter bar; again to the Quick Launch icons (Return opens the one selected) |
= or click 📌 | pin / unpin the window |
| drag a pinned row | reorder pins |
| drag a row onto a group box (or onto + New group, shown while dragging) | add the window to that group |
| click a window in a group box · hover it and click × | switch to that window · take it out of the group |
| click an item in Quick Jumps | open that folder, file or URL |
` or click the filter bar | keep the list open after releasing Option |
| type letters | filter (every word must match the title or app name); Backspace edits |
| hover / scroll wheel | select the row under the mouse / step through rows |
| Return or click a row | switch to that window |
| Esc | clear the filter, or close the list if the filter is empty |
| click outside (when kept open) | close |
Any mouse use in the list also keeps it open, so letting go of Option doesn't switch mid-click.
Scope and limits:
Menu bar → AltTab → Preferences… (⌘,) or About AltTab
| General | Quick Launch | Quick Jumps | About |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
Requires macOS 13+ and Swift 5.9+. The Command Line Tools are enough; Xcode is only needed for universal (arm64 + x86_64) builds.
./scripts/build-app.sh --open # build, sign, install to ~/Applications, launch
./scripts/build-app.sh --no-install
ALTTAB_UNIVERSAL=1 ./scripts/build-app.sh --no-install # needs Xcode
./scripts/make-dmg.sh # build, then package build/AltTab-<version>.dmg and .zip
Signing: macOS ties the Accessibility grant to the code signature. The script signs with an identity named AltTab Dev if one exists, otherwise with your first Apple Development certificate. Either one stays the same across rebuilds, so the grant sticks. If neither exists, the script falls back to an ad-hoc signature, and you'd have to re-grant Accessibility after every build. To create a stable identity for free: Keychain Access → Certificate Assistant → Create a Certificate… (Name AltTab Dev, Type Code Signing). If the grant gets stuck, run tccutil reset Accessibility com.sr3d.AltTab and relaunch.
CI: both workflows run scripts/make-dmg.sh to build a universal, ad-hoc-signed app and package it as a DMG and a zip. build.yml runs on every push to main and every pull request, and uploads them as a workflow artifact. release.yml runs when a v* tag is pushed, and publishes a GitHub Release with both files attached. GitHub builds everything on its own macOS machines.
Releasing: either run ./release from an up-to-date main, or open the Actions tab → Make New Release → Run workflow. Both bump the version in Resources/Info.plist, point the README's Download button at the new AltTab-X.Y.Z.dmg, commit, tag vX.Y.Z and push; GitHub then builds and publishes the release.
./release # 0.1.0 -> 0.1.1
./release minor # 0.1.0 -> 0.2.0 (also: major, or an exact version like 1.4.2)
To rebuild the files for an existing tag, run Release by hand from the Actions tab and enter the tag.
Developer tools:
swift run FocusSpike lists on-screen windows; swift run FocusSpike <title> focuses one..build/debug/AltTab --screenshots docs regenerates the README screenshots. It renders the real panel with made-up window titles.swift scripts/make-icon.swift regenerates the app icon.defaults write com.sr3d.AltTab debug -bool true # then relaunch
log stream --predicate 'subsystem == "com.sr3d.AltTab"'
| File | Role |
|---|---|
Sources/AltTab/HotkeyTap.swift | CGEventTap for Option+Tab (and Cmd+Tab when enabled) and the keys used while the list is open. Keys are swallowed so they never reach the app underneath. |
Sources/AltTab/NativeCommandTab.swift | Turns the macOS Cmd+Tab switcher off or on (CGSSetSymbolicHotKeyEnabled), and restores it on quit, on kill signals and after crashes. |
Sources/AltTab/WindowTracker.swift | Builds the focus history from an AXObserver per app plus workspace activation events. It also keeps a background cache of each app's windows: some apps take seconds to answer Accessibility queries, so a keypress never waits on them. |
Sources/AltTab/Switcher.swift | List state: ordering, pins, filter, number keys, stay-open mode |
Sources/AltTab/QuickLaunch.swift, QuickLaunchEditor.swift | Quick Launch apps (stored by path and bundle ID) and their Preferences list |
Sources/AltTab/SwitcherPanel.swift | The non-activating HUD panel and its mouse handling, plus the group boxes and Quick Jumps windows beside it |
Sources/AltTab/QuickJumps.swift, QuickJumpsEditor.swift | Quick Jumps bookmarks (stored as URLs) and their Preferences list |
Sources/AltTab/WindowGroups.swift | Window groups: members, pruning closed windows, cached previews |
Sources/AltTabCore/WindowThumbnail.swift | Window previews (CGWindowListCreateImage, resolved at runtime) and the Screen Recording permission check |
Sources/AltTabCore/WindowList.swift | On-screen windows (CGWindowListCopyWindowInfo), matched to Accessibility windows via _AXUIElementGetWindow |
Sources/AltTabCore/WindowFocuser.swift | Brings exactly one window forward: _SLPSSetFrontProcessWithOptions + a synthetic make-key event + an AX raise |
Sources/AltTabCore/PrivateAPIs.swift | Private SkyLight calls, loaded with dlopen. If a symbol is missing, focusing falls back to public APIs. |
Raising one window without bringing its whole app forward isn't possible with public APIs alone. The technique comes from yabai and Hammerspoon, as refined by alt-tab-macos. This project is a separate, much smaller app and isn't affiliated with alt-tab-macos.
macOS doesn't let one app change the window level of another app's window. yabai can do it by injecting code into the Dock, but that requires partially disabling System Integrity Protection. The only SIP-safe workaround is a live ScreenCaptureKit mirror in a floating panel, which is view-only and needs Screen Recording permission.
App Switcher for MacOS: Switch between Windows from different apps.
Swift
2
16 commits
updated Oct 6, 2026
A tiny macOS menu-bar app that switches between windows, not apps.
Cmd+Tab activates a whole app, so every window of that app jumps forward and buries what you were looking at. Option+Tab brings back just the one window you were in before. Hold Option to pick any window from a list that you can pin, number and filter.
macOS 13 or later · Apple Silicon and Intel · install steps · all releases

AltTab.app onto the Applications shortcut.xattr -dr com.apple.quarantine /Applications/AltTab.app
AltTab ⚠︎ until the permission is active, then just AltTab.Turn on Launch at Login from the menu-bar menu or Preferences.
| Filter | Larger font | Cmd+Tab app list |
|---|---|---|
![]() | ![]() | ![]() |
![]()

| Keys | Action |
|---|---|
| Option+Tab (tap) | switch to the previous window |
| Option held + Tab | open the list; release Option to switch |
| Cmd+Tab (if enabled in Preferences) | the same, listing windows (default) or running apps, as chosen in Preferences |
| Option+Tab with Cmd+Tab listing windows | the same, listing running apps instead (Option+Tab keeps listing windows when Cmd+Tab lists apps) |
While the list is open:
| Keys / mouse | Action |
|---|---|
| Tab / → / ↓ · Shift+Tab / ← / ↑ | move the selection |
| 1–9, 0 | jump straight to that row |
| Shift+1–9, 0, the app's own key (e.g. C) or click an icon in the top bar | open that Quick Launch app |
| Shift+Tab / ↑ on the first row | move to the filter bar; again to the Quick Launch icons (Return opens the one selected) |
= or click 📌 | pin / unpin the window |
| drag a pinned row | reorder pins |
| drag a row onto a group box (or onto + New group, shown while dragging) | add the window to that group |
| click a window in a group box · hover it and click × | switch to that window · take it out of the group |
| click an item in Quick Jumps | open that folder, file or URL |
` or click the filter bar | keep the list open after releasing Option |
| type letters | filter (every word must match the title or app name); Backspace edits |
| hover / scroll wheel | select the row under the mouse / step through rows |
| Return or click a row | switch to that window |
| Esc | clear the filter, or close the list if the filter is empty |
| click outside (when kept open) | close |
Any mouse use in the list also keeps it open, so letting go of Option doesn't switch mid-click.
Scope and limits:
Menu bar → AltTab → Preferences… (⌘,) or About AltTab
| General | Quick Launch | Quick Jumps | About |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
Requires macOS 13+ and Swift 5.9+. The Command Line Tools are enough; Xcode is only needed for universal (arm64 + x86_64) builds.
./scripts/build-app.sh --open # build, sign, install to ~/Applications, launch
./scripts/build-app.sh --no-install
ALTTAB_UNIVERSAL=1 ./scripts/build-app.sh --no-install # needs Xcode
./scripts/make-dmg.sh # build, then package build/AltTab-<version>.dmg and .zip
Signing: macOS ties the Accessibility grant to the code signature. The script signs with an identity named AltTab Dev if one exists, otherwise with your first Apple Development certificate. Either one stays the same across rebuilds, so the grant sticks. If neither exists, the script falls back to an ad-hoc signature, and you'd have to re-grant Accessibility after every build. To create a stable identity for free: Keychain Access → Certificate Assistant → Create a Certificate… (Name AltTab Dev, Type Code Signing). If the grant gets stuck, run tccutil reset Accessibility com.sr3d.AltTab and relaunch.
CI: both workflows run scripts/make-dmg.sh to build a universal, ad-hoc-signed app and package it as a DMG and a zip. build.yml runs on every push to main and every pull request, and uploads them as a workflow artifact. release.yml runs when a v* tag is pushed, and publishes a GitHub Release with both files attached. GitHub builds everything on its own macOS machines.
Releasing: either run ./release from an up-to-date main, or open the Actions tab → Make New Release → Run workflow. Both bump the version in Resources/Info.plist, point the README's Download button at the new AltTab-X.Y.Z.dmg, commit, tag vX.Y.Z and push; GitHub then builds and publishes the release.
./release # 0.1.0 -> 0.1.1
./release minor # 0.1.0 -> 0.2.0 (also: major, or an exact version like 1.4.2)
To rebuild the files for an existing tag, run Release by hand from the Actions tab and enter the tag.
Developer tools:
swift run FocusSpike lists on-screen windows; swift run FocusSpike <title> focuses one..build/debug/AltTab --screenshots docs regenerates the README screenshots. It renders the real panel with made-up window titles.swift scripts/make-icon.swift regenerates the app icon.defaults write com.sr3d.AltTab debug -bool true # then relaunch
log stream --predicate 'subsystem == "com.sr3d.AltTab"'
| File | Role |
|---|---|
Sources/AltTab/HotkeyTap.swift | CGEventTap for Option+Tab (and Cmd+Tab when enabled) and the keys used while the list is open. Keys are swallowed so they never reach the app underneath. |
Sources/AltTab/NativeCommandTab.swift | Turns the macOS Cmd+Tab switcher off or on (CGSSetSymbolicHotKeyEnabled), and restores it on quit, on kill signals and after crashes. |
Sources/AltTab/WindowTracker.swift | Builds the focus history from an AXObserver per app plus workspace activation events. It also keeps a background cache of each app's windows: some apps take seconds to answer Accessibility queries, so a keypress never waits on them. |
Sources/AltTab/Switcher.swift | List state: ordering, pins, filter, number keys, stay-open mode |
Sources/AltTab/QuickLaunch.swift, QuickLaunchEditor.swift | Quick Launch apps (stored by path and bundle ID) and their Preferences list |
Sources/AltTab/SwitcherPanel.swift | The non-activating HUD panel and its mouse handling, plus the group boxes and Quick Jumps windows beside it |
Sources/AltTab/QuickJumps.swift, QuickJumpsEditor.swift | Quick Jumps bookmarks (stored as URLs) and their Preferences list |
Sources/AltTab/WindowGroups.swift | Window groups: members, pruning closed windows, cached previews |
Sources/AltTabCore/WindowThumbnail.swift | Window previews (CGWindowListCreateImage, resolved at runtime) and the Screen Recording permission check |
Sources/AltTabCore/WindowList.swift | On-screen windows (CGWindowListCopyWindowInfo), matched to Accessibility windows via _AXUIElementGetWindow |
Sources/AltTabCore/WindowFocuser.swift | Brings exactly one window forward: _SLPSSetFrontProcessWithOptions + a synthetic make-key event + an AX raise |
Sources/AltTabCore/PrivateAPIs.swift | Private SkyLight calls, loaded with dlopen. If a symbol is missing, focusing falls back to public APIs. |
Raising one window without bringing its whole app forward isn't possible with public APIs alone. The technique comes from yabai and Hammerspoon, as refined by alt-tab-macos. This project is a separate, much smaller app and isn't affiliated with alt-tab-macos.
macOS doesn't let one app change the window level of another app's window. yabai can do it by injecting code into the Dock, but that requires partially disabling System Integrity Protection. The only SIP-safe workaround is a live ScreenCaptureKit mirror in a floating panel, which is view-only and needs Screen Recording permission.