alt = the modifier  ·  hjkl = the compass  ·  a workspace is a tree  ·  join-with is the only command that adds a level  ·  swap keeps the shape and trades the contents  ·  resize smart follows the parent's axis  ·  there is no always-on-top  ·  duplicate binding key = "Ill-formed key"  ·  atree shows you the tree  ·  service mode = alt-shift-;  ·  panic button = r  · 
AeroSpace 0.21.3-Beta · macOS · personal config

The Window Tree

Every layout command acts on one thing: the position of the focused window inside a tree. Learn the tree and the keys stop being arbitrary.

alt is
the mod
config~/.aerospace.toml
reloadon save (auto)
service modealt-shift-;
see the treeatree
panic buttonalt-shift-; then r

01 The model, and everything follows from it

A workspace holds one root container. Containers hold children; windows are the leaves. Each container has an orientation (horizontal = side by side, vertical = stacked) and a layout (tiles = all visible, accordion = overlapping).

root (h_tiles)
├── iTerm2
└── container (v_tiles)
    ├── Teams
    └── Finder
iTerm2
Teams
Finder
what that tree looks like
container   branch

The tree is the rectangle subdivision on screen. A horizontal container is literally a vertical dividing line. That is why gaps at 0 make the structure invisible: the cuts vanish into the window chrome.

A tiled window has no free edges. Every edge is shared with a sibling, so its size is a share of its parent container rather than a property of the window. This is why there is no "resize left/right/up/down". There is only bigger or smaller, along one axis at a time.

02 The compass: one direction, four verbs

h j k l is always left / down / up / right. Only the verb changes, and only two of the four change the tree's shape.

KeysCommandWhat it does to the treeShape
alt+hjkl focusmoves your cursor around the treeunchanged
alt+shift+hjkl movepulls the window out and re-inserts it elsewherechanges
alt+shift+;thenhjkl swapexchanges two windows in place; the contents tradeunchanged
alt+shift+;thenalt+shift+hjkl join-withadds a level, putting two windows under a new containerchanges
move vs swap
the one that trips you

Shape wrong, use move. Windows wrong, use swap.

If the layout is right but the wrong apps sit in the slots, swap is the tool. It leaves the tree untouched. Reaching for move there is what wrecks a layout you just built. Swap is its own inverse, so running it again undoes it.

join-with
the only nester

You choose the pair, never the axis.

Want one tall window on the left and two stacked on the right? Focus the rightmost window and join-with left. The new container's orientation gets flipped for you by the opposite-orientation normalization, so the only decision is which two windows to group.

03 Resize: the axis comes from the parent

One key pair covers left/right in one place and up/down in another, because smart reads the axis off the parent container. atree tells you which you will get.

KeysCommandEffect
alt+/= resize smart ∓50 parent h_tiles gives width; parent v_tiles gives height
alt+shift+/= resize smart-opposite ∓50 the other axis, meaning the split one level up the tree
alt+0 balance-sizes evens out every split while keeping the nesting
  1. 50 is points, not percent. Each press moves the split by about 50pt. Change the number for finer or faster control.
  2. A sign makes it relative. +50 and -50 nudge. A bare 800 sets the dimension absolutely.
  3. Reach for balance-sizes before flatten. It fixes drifted sizes and keeps your nesting, where flatten-workspace-tree destroys it.

smart-opposite does nothing in a flat layout, and that is correct behaviour. It searches for an ancestor container on the opposite axis. In a flat row (root: h_tiles, no nesting) there is no vertical container to resize, so the lookup returns nothing and the command fails. It fails with the message "resize command doesn't support floating windows yet", which is really the catch-all branch for "no container on that axis" rather than a statement about floating. A hotkey press swallows the error, so it simply looks dead. Build some nesting with join-with and it starts working.

04 Layout, float, fullscreen

KeysCommandEffect
alt+/ layout tiles horizontal vertical force tiles; if already tiles, flip the orientation of the container you are standing in
alt+, layout accordion focused window takes the space; siblings collapse to slivers
alt+f layout floating tiling toggle the window out of the tree and back into it
alt+z fullscreen temporary zoom covering the whole workspace
alt+shift+z fullscreen --no-outer-gaps edge-to-edge zoom

Multi-argument layout is a toggle. The first argument that does not already describe the current state wins. So layout floating tiling floats a tiled window and tiles a floating one, giving both directions from one binding.

There is no always-on-top. At all. A floating window sits above the tiles while it holds focus, but focusing a tiled window can cover it again. Upstream issues #4 ("floating windows always on top") and #2 ("sticky floating windows") are both still open. To keep something visible while working elsewhere, three honest options:

  • alt-z fullscreen. Reliably on top because it covers everything.
  • alt-, accordion. Siblings stay visible as slivers.
  • A separate utility. AeroSpace will not do it.

05 Workspaces and monitors

KeysEffect
alt+19/a-z jump to that workspace
alt+shift+19/a-z throw the focused window to that workspace
alt+tab back and forth between the last two workspaces
alt+shift+tab move this whole workspace to the next monitor

f and z are no longer workspaces. Those two letters were freed to carry alt-f for float and alt-z for fullscreen, then removed from persistent-workspaces so no unreachable workspace is left behind.

06 Service mode

Enter with alt+shift+; then press one key. A modal layer, so plain letters are free. Every binding returns you to main mode.

hjklswaptrade the focused window with its neighbour
alt+shift+hjkljoin-withcreate a nested container
rflatten-workspace-treethe panic button; destroys all nesting
flayout floating tilingfloat and tile toggle, same as alt-f
qcloseclose the focused window
backspaceclose-all-windows-but-currentkeep only this window
escreload-configreload and leave the mode

07 Seeing the tree

AeroSpace has no list-tree command. atree reconstructs it from depth-first window order plus each window's parent-container layout.

atree the focused workspace's tree
atree 3 a specific workspace
atree -w redraw every second, parked in a side pane

atree -w is the learning loop. Keep it visible, press your keys, watch the tree redraw. Without it you press a key and guess.

Everything is also a plain CLI command, which is the cheapest way to experiment. The name and its effect arrive together, and nothing is destroyed:

aerospace list-windows --workspace focused   # what is here
aerospace join-with left                       # reshape
aerospace swap left                            # trade two windows
aerospace balance-sizes                        # even the splits
aerospace flatten-workspace-tree               # undo it all

08 Traps worth remembering

"Ill-formed key"
config error

It means a duplicate binding key.

Not a bad key name. Paste a config block that repeats a key already defined above and AeroSpace reports the second occurrence as ill-formed. Check for duplicates before suspecting the name. These are all valid:

minus equal 0 left down up right enter esc semicolon slash backslash space tab

config-version = 1
symptom

A failed parse falls back to defaults.

If the diagnostics window warns that your config-version is 1 while your file says 2, that is the built-in default config talking. The real problem is the error above it, and the warning disappears once the file parses.

"doesn't support floating windows"
misleading

Usually means "no container on that axis".

Seen from resize smart-opposite on a perfectly tiled window. It is the catch-all branch taken when the ancestor lookup returns nothing. Add nesting and it resolves.

gaps = 0
invisible tree

Two dark windows touching read as one.

With zero inner gaps the dividing lines vanish into window chrome and the subdivision becomes unreadable. A few points of gaps.inner is a learning aid rather than decoration.

alt-<letter> is eaten
terminal cost

macOS delivers it to the window manager first.

Every alt-<letter> workspace binding takes that combination away from your terminal and whatever runs inside it. Free the specific letters you would rather keep instead of moving the whole scheme onto a harder modifier.

single-child container
transient

Normalization usually collapses it.

If atree shows a container holding one window, it is left over from mid-experiment shuffling. flatten-workspace-tree clears it back to a flat row.

The whole system reduces to one question: where am I in the tree?

Focus moves you around it. Move and join-with reshape it. Swap trades its contents. Resize changes the split you are standing on. Every key is one of those four, and atree answers the question before you press anything.