i3-style window management on macOS with AeroSpace
Published
AeroSpace brings i3-like tiling, keyboard navigation, and numbered workspaces to macOS. Workspaces can move freely between two displays.
1. Install the apps
Install Homebrew first. AeroSpace is required:
brew install --cask nikitabobko/tap/aerospace
Ghostty and Swift Quit are optional:
brew install --cask swift-quit
brew install --cask ghostty
- AeroSpace manages windows and workspaces.
- Swift Quit optionally quits an application after its last window closes.
- Ghostty is an optional replacement for built-in Terminal.
Open every app once after installation.
2. Allow Accessibility access
Open:
System Settings → Privacy & Security → Accessibility
Enable:
- AeroSpace
- Swift Quit, if installed
Restart each app after granting access.
3. Disable conflicting macOS features
Open System Settings → Desktop & Dock:
- Turn off Stage Manager.
- Turn off window tiling by dragging windows to screen edges.
- Turn off the Option-key window tiling shortcut.
- Turn off automatic Space rearrangement.
- Turn off Displays have separate Spaces for better AeroSpace stability. Log out and back in after changing it.
Open System Settings → Trackpad → More Gestures and disable macOS horizontal Space switching plus the three-finger upward Mission Control gesture. Use AeroSpace keyboard shortcuts instead.
4. Create the AeroSpace config
Create ~/.aerospace.toml:
config-version = 2
start-at-login = true
auto-reload-config = true
after-startup-command = [
# Optional: remove this line if Swift Quit is not installed.
'exec-and-forget open -gj -b onebadidea.Swift-Quit',
]
default-root-container-layout = 'tiles'
default-root-container-orientation = 'auto'
accordion-padding = 24
persistent-workspaces = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']
focus-follows-mouse.enabled = false
automatically-unhide-macos-hidden-apps = true
on-window-detected = [
# Optional Ghostty routing; remove this block if Ghostty is not installed.
{
if = 'test %{app-bundle-id} = com.mitchellh.ghostty',
run = [
'layout tiling',
'move-node-to-workspace 1',
],
},
{
if = 'test %{app-bundle-id} = com.microsoft.VSCode',
run = 'move-node-to-workspace 2',
},
{
if = 'test %{app-bundle-id} = com.apple.Safari',
run = 'move-node-to-workspace 3',
},
{
if = 'test %{app-bundle-id} = md.obsidian',
run = 'move-node-to-workspace 5',
},
{
if = 'test %{app-bundle-id} = com.hnc.Discord || test %{app-bundle-id} = ru.keepcoder.Telegram',
run = 'move-node-to-workspace 6',
},
{
if = 'test %{app-bundle-id} = com.spotify.client',
run = 'move-node-to-workspace 7',
},
]
[mode.main.binding]
cmd-alt-h = []
# Optional Ghostty launcher.
cmd-enter = 'exec-and-forget open -na Ghostty'
alt-slash = 'layout tiles horizontal vertical'
alt-comma = 'layout accordion horizontal vertical'
cmd-h = 'focus left'
cmd-j = 'focus down'
cmd-k = 'focus up'
cmd-l = 'focus right'
cmd-shift-h = 'move left'
cmd-shift-j = 'move down'
cmd-shift-k = 'move up'
cmd-shift-l = 'move right'
cmd-ctrl-m = 'focus-monitor --wrap-around next'
cmd-ctrl-shift-m = 'move-node-to-monitor --wrap-around --focus-follows-window next'
alt-minus = 'resize smart -50'
alt-equal = 'resize smart +50'
cmd-1 = 'workspace 1'
cmd-2 = 'workspace 2'
cmd-3 = 'workspace 3'
cmd-4 = 'workspace 4'
cmd-5 = 'workspace 5'
cmd-6 = 'workspace 6'
cmd-7 = 'workspace 7'
cmd-8 = 'workspace 8'
cmd-9 = 'workspace 9'
cmd-shift-1 = 'move-node-to-workspace 1'
cmd-shift-2 = 'move-node-to-workspace 2'
cmd-shift-3 = 'move-node-to-workspace 3'
cmd-shift-4 = 'move-node-to-workspace 4'
cmd-shift-5 = 'move-node-to-workspace 5'
cmd-shift-6 = 'move-node-to-workspace 6'
cmd-shift-7 = 'move-node-to-workspace 7'
cmd-shift-8 = 'move-node-to-workspace 8'
cmd-shift-9 = 'move-node-to-workspace 9'
alt-tab = 'workspace-back-and-forth'
cmd-p = 'move-workspace-to-monitor --wrap-around next'
cmd-shift-semicolon = 'mode service'
[mode.service.binding]
esc = ['reload-config', 'mode main']
r = ['flatten-workspace-tree', 'mode main']
f = ['layout floating tiling', 'mode main']
b = ['balance-sizes', 'mode main']
backspace = ['close-all-windows-but-current', 'mode main']
alt-shift-h = ['join-with left', 'mode main']
alt-shift-j = ['join-with down', 'mode main']
alt-shift-k = ['join-with up', 'mode main']
alt-shift-l = ['join-with right', 'mode main']
Reload it:
aerospace reload-config
5. Workspace layout
- 1: Ghostty
- 2: Visual Studio Code
- 3: Safari
- 4: spare workspace
- 5: Obsidian
- 6: Discord and Telegram
- 7: Spotify
- 8–9: spare workspaces
New app windows automatically move to their assigned workspace.
6. Main shortcuts
Command + 1…9: open workspaceCommand + Shift + 1…9: move window to workspaceCommand + H/J/K/L: focus left/down/up/rightCommand + Shift + H/J/K/L: move windowCommand + Control + M: switch focus between the two displaysCommand + Control + Shift + M: move window to next displayCommand + P: move current workspace to other displayCommand + Enter: open GhosttyOption + /: tiled layoutOption + ,: accordion layoutOption + Tab: previous workspace
7. Optional: Configure Swift Quit
Open Swift Quit and use:
- Close delay: 2 seconds
- Behaviour: quit every app except excluded apps
- Launch at login: enabled
Exclude background or communication apps that should keep running, for example AeroSpace, Swift Quit, VPN clients, Discord, Telegram, Spotify, and Raycast.
Optional: Configure Ghostty splits
Native Ghostty tabs appear as separate macOS windows. Use internal splits instead.
Create ~/.config/ghostty/config:
keybind = super+t=new_split:auto
keybind = super+shift+[=goto_split:previous
keybind = super+shift+]=goto_split:next
keybind = ctrl+tab=goto_split:next
keybind = ctrl+shift+tab=goto_split:previous
Command + T now creates a split instead of a native tab. Press Command + Shift + , inside Ghostty to reload its config.
Fullscreen and terminal notes
I have not found a proper native-fullscreen fix with two displays yet, so I do not use it. I also avoid built-in Terminal and Ghostty native tabs; both conflict with AeroSpace. Ghostty splits work well.
No comments yet.