After a year of struggling with the frustrations and bugs of yabai / skhd, I learned of a newer window manager named Aerospace and it has completely changed my workflow. I’m never going back! There were a few things that I wanted in a tiling window manager:
- No need for the mouse
- I want to get to the desired window as easily as possible
- Easy configuration
With Yabai / skhd, I was able to avoid the mouse, I (mostly) was able to get to the window I wanted, but configuration was pretty involved. My previous system had a different philosophy to window management that worked at the time, but I have found is far inferior. At the time I used 2 external monitors and my laptop’s screen. I used macbook’s native desktops and all of my keybindings were focused on changing between monitors, then changing between the desktops on those monitors.
Once I had all of the windows in the desired location, this worked pretty well. Each application had a specified location, which made it easy to find the right one. Main screen had neovim -> datagrip -> postman, second monitor had a browser, laptop screen had slack -> spotify. While the system made sense, it required a lot of mouse work or keystrokes to get everything put into the correct location. In fact the bug that finally pushed me over the edge was any time I unplugged my laptop, all of the desktops would be jumbled, requiring time to sort them out.
Enter Aerospace. The philosophical difference between Aerospace and my previous setup, is that instead of organizing and traversing the windows according to physical monitors, the focus is on what Aerospace coins ‘virtual workspaces’. Practically speaking, it allows the user to switch directly to a workspace, regardless of what monitor that workspace is on.
As an example, let’s say I have neovim on my right monitor and my browser on the left. With the old philosophy I would execute a keycommand that would switch focus to the monitor to the left. With Aerospace I would execute a keycommand that takes me to the browser. It’s a more direct and concrete command.
The beauty of Aerospace comes not only in the navigation, but the configuration as well, allowing the user to:
- assign applications to workspaces
- assign workspaces to monitors
- move applications to workspaces
- move workspaces to monitors
Here is my aerospace configuration file, it is based off of the default file that aerospace provides. Feel free to peruse the options, but the area I would like to highlight is the workspaces and monitor configuration at the bottom…
# Place a copy of this config to ~/.aerospace.toml
# After that, you can edit ~/.aerospace.toml to your liking
# You can use it to add commands that run after login to macOS user session.
# 'start-at-login' needs to be 'true' for 'after-login-command' to work
# Available commands: https://nikitabobko.github.io/AeroSpace/commands
after-login-command = []
# You can use it to add commands that run after AeroSpace startup.
# 'after-startup-command' is run after 'after-login-command'
# Available commands : https://nikitabobko.github.io/AeroSpace/commands
after-startup-command = []
# Start AeroSpace at login
start-at-login = true
# Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalization
enable-normalization-flatten-containers = true
enable-normalization-opposite-orientation-for-nested-containers = true
# See: https://nikitabobko.github.io/AeroSpace/guide#layouts
# The 'accordion-padding' specifies the size of accordion padding
# You can set 0 to disable the padding feature
accordion-padding = 10
# Possible values: tiles|accordion
default-root-container-layout = 'tiles'
# Possible values: horizontal|vertical|auto
# 'auto' means: wide monitor (anything wider than high) gets horizontal orientation,
# tall monitor (anything higher than wide) gets vertical orientation
default-root-container-orientation = 'auto'
# Possible values: (qwerty|dvorak)
# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping
key-mapping.preset = 'qwerty'
# Mouse follows focus when focused monitor changes
# Drop it from your config, if you don't like this behavior
# See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks
# See https://nikitabobko.github.io/AeroSpace/commands#move-mouse
# Fallback value (if you omit the key): on-focused-monitor-changed = []
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']
# Gaps between windows (inner-*) and between monitor edges (outer-*).
# Possible values:
# - Constant: gaps.outer.top = 8
# - Per monitor: gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24]
# In this example, 24 is a default value when there is no match.
# Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'.
# See: https://nikitabobko.github.io/AeroSpace/guide#assign-workspaces-to-monitors
[gaps]
inner.horizontal = 2
inner.vertical = 2
outer.left = 2
outer.bottom = 2
outer.top = 2
outer.right = 2
# 'main' binding mode declaration
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
# 'main' binding mode must be always presented
# Fallback value (if you omit the key): mode.main.binding = {}
[mode.main.binding]
# All possible keys:
# - Letters. a, b, c, ..., z
# - Numbers. 0, 1, 2, ..., 9
# - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9
# - F-keys. f1, f2, ..., f20
# - Special keys. minus, equal, period, comma, slash, backslash, quote, semicolon, backtick,
# leftSquareBracket, rightSquareBracket, space, enter, esc, backspace, tab
# - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual,
# keypadMinus, keypadMultiply, keypadPlus
# - Arrows. left, down, up, right
# All possible modifiers: cmd, alt, ctrl, shift
# All possible commands: https://nikitabobko.github.io/AeroSpace/commands
# See: https://nikitabobko.github.io/AeroSpace/commands#exec-and-forget
# You can uncomment the following lines to open up terminal with alt + enter shortcut (like in i3)
# alt-enter = '''exec-and-forget osascript -e '
# tell application "Terminal"
# do script
# activate
# end tell'
# '''
cmd-h = [] # Disable "hide application"
cmd-alt-h = [] # Disable "hide others"
# See: https://nikitabobko.github.io/AeroSpace/commands#layout
alt-slash = 'layout tiles horizontal vertical'
alt-comma = 'layout accordion horizontal vertical'
# See: https://nikitabobko.github.io/AeroSpace/commands#focus
alt-h = 'focus left'
alt-j = 'focus down'
alt-k = 'focus up'
alt-l = 'focus right'
# See: https://nikitabobko.github.io/AeroSpace/commands#move
alt-shift-h = 'move left'
alt-shift-j = 'move down'
alt-shift-k = 'move up'
alt-shift-l = 'move right'
# See: https://nikitabobko.github.io/AeroSpace/commands#resize
alt-shift-minus = 'resize smart -50'
alt-shift-equal = 'resize smart +50'
# See: https://nikitabobko.github.io/AeroSpace/commands#workspace
# alt-1 = 'workspace 1'
# alt-2 = 'workspace 2'
# alt-3 = 'workspace 3'
# alt-4 = 'workspace 4'
# alt-5 = 'workspace 5'
# alt-6 = 'workspace 6'
# alt-7 = 'workspace 7'
# alt-8 = 'workspace 8'
# alt-9 = 'workspace 9'
# alt-a = 'workspace A'
alt-b = 'workspace B' # Browser
alt-c = 'workspace C' # keymapp
alt-d = 'workspace D' # Discord
# alt-e = 'workspace E'
# alt-f = 'workspace F'
alt-g = 'workspace G' # dataGrip
alt-i = 'workspace I' # Intellij
alt-m = 'workspace M' # messaging /media (texting and spotify)
# alt-n = 'workspace N'
# alt-o = 'workspace O'
alt-p = 'workspace P' # Postman
# alt-q = 'workspace Q'
# alt-r = 'workspace R'
alt-s = 'workspace S' # Slack
alt-t = 'workspace T' # Terminal (currently alacritty)
# alt-u = 'workspace U'
# alt-v = 'workspace V'
# alt-w = 'workspace W'
# alt-x = 'workspace X'
# alt-y = 'workspace Y'
alt-z = 'workspace Z' # zoom
# See: https://nikitabobko.github.io/AeroSpace/commands#move-node-to-workspace
# alt-shift-1 = 'move-node-to-workspace 1'
# alt-shift-2 = 'move-node-to-workspace 2'
# alt-shift-3 = 'move-node-to-workspace 3'
# alt-shift-4 = 'move-node-to-workspace 4'
# alt-shift-5 = 'move-node-to-workspace 5'
# alt-shift-6 = 'move-node-to-workspace 6'
# alt-shift-7 = 'move-node-to-workspace 7'
# alt-shift-8 = 'move-node-to-workspace 8'
# alt-shift-9 = 'move-node-to-workspace 9'
# alt-shift-a = 'move-node-to-workspace A'
alt-shift-b = 'move-node-to-workspace B'
alt-shift-c = 'move-node-to-workspace C'
alt-shift-d = 'move-node-to-workspace D'
# alt-shift-e = 'move-node-to-workspace E'
# alt-shift-f = 'move-node-to-workspace F'
alt-shift-g = 'move-node-to-workspace G'
alt-shift-i = 'move-node-to-workspace I'
alt-shift-m = 'move-node-to-workspace M'
# alt-shift-n = 'move-node-to-workspace N'
# alt-shift-o = 'move-node-to-workspace O'
alt-shift-p = 'move-node-to-workspace P'
# alt-shift-q = 'move-node-to-workspace Q'
# alt-shift-r = 'move-node-to-workspace R'
alt-shift-s = 'move-node-to-workspace S'
alt-shift-t = 'move-node-to-workspace T'
# alt-shift-u = 'move-node-to-workspace U'
# alt-shift-v = 'move-node-to-workspace V'
# alt-shift-w = 'move-node-to-workspace W'
# alt-shift-x = 'move-node-to-workspace X'
# alt-shift-y = 'move-node-to-workspace Y'
alt-shift-z = 'move-node-to-workspace Z'
# See: https://nikitabobko.github.io/AeroSpace/commands#workspace-back-and-forth
alt-tab = 'workspace-back-and-forth'
# See: https://nikitabobko.github.io/AeroSpace/commands#move-workspace-to-monitor
alt-shift-tab = 'move-workspace-to-monitor --wrap-around next'
# See: https://nikitabobko.github.io/AeroSpace/commands#mode
alt-shift-semicolon = 'mode service'
# 'service' binding mode declaration.
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
[mode.service.binding]
esc = ['reload-config', 'mode main']
r = ['flatten-workspace-tree', 'mode main'] # reset layout
f = ['layout floating tiling', 'mode main'] # Toggle between floating and tiling layout
backspace = ['close-all-windows-but-current', 'mode main']
# sticky is not yet supported https://github.com/nikitabobko/AeroSpace/issues/2
#s = ['layout sticky tiling', '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']
# app assignments
# to find the app ids run `aerospace list-apps`
[[on-window-detected]]
if.app-id = 'company.thebrowser.Browser'
run = "move-node-to-workspace B"
[[on-window-detected]]
if.app-id = 'com.hnc.Discord'
run = "move-node-to-workspace D"
[[on-window-detected]]
if.app-id = 'com.jetbrains.datagrip'
run = "move-node-to-workspace G"
[[on-window-detected]]
if.app-id = 'com.jetbrains.intellij'
run = "move-node-to-workspace I"
[[on-window-detected]]
if.app-id = 'com.spotify.client'
run = "move-node-to-workspace M"
[[on-window-detected]]
if.app-id = 'com.apple.MobileSMS'
run = "move-node-to-workspace M"
[[on-window-detected]]
if.app-id = 'org.alacritty'
run = "move-node-to-workspace T"
[[on-window-detected]]
if.app-id = 'com.tinyspeck.slackmacgap'
run = "move-node-to-workspace S"
[[on-window-detected]]
if.app-id = 'us.zoom.xos'
run = "move-node-to-workspace Z"
# built-in for the laptop, LG for the wide screen, Dell for the secondary
[workspace-to-monitor-force-assignment]
B = 'DELL'
T = 'LG'
M = 'LG'
S = 'LG'
D = 'LG'
Z = 'built-in'
My approach to workspaces is assigning an application to a workspace and remembering that workspace via a mnemonic device. For instance, B is for Browser, T is for Terminal (sounds like one of my 3 year old’s alphabet books). Beyond that, I am able to assign these workspaces to a monitor. In terms of workflow, it allows me to achieve what I attempted to with my previous setup, but with even more efficiency. I am able to say the terminal goes on that screen, and I want to be able to execute a single keystroke to get there.
closing thoughts
A bonus side effect of using Aerospace has been reducing my screens. I have moved to only two screens, and when I can only use one screen, I can still operate just as efficiently.
I didn’t discuss the tiling portion of Aerospace much because I don’t use it much. I have one space where I have 2 applications split, but other than that I stick to one application per workspace. If spliting the screen is part of your workflow, it has great support for that as well.
In summary, I highly recommend checking out Aerospace and giving it a try!