So you want i3wm on MacOS?
I recently got a Macbook from my new employer and had to leave my Linux setup behind. 2021 was the first time in my life I owned an Apple product. MacOS is fine, but switching my workflow was painful.
I’d been using i3 under Linux for years and couldn’t imagine working without it. A colleague (thanks Marcel aka @snowiow!) had gone through the same thing and pointed me in the right direction. The result covers about 90% of what I loved about i3.
yabai + skhd (i3 replacement)
yabai is a tiling window manager for macOS. It extends the built-in window manager and lets you control windows, spaces, and displays from the command line or via keyboard shortcuts.
Install it with brew:
brew install koekeishiya/formulae/yabai
Start the service and grant the accessibility permissions it asks for:
brew services start yabai
yabai reads its config from ~/.yabairc:
touch ~/.yabairc
Here’s mine:
#!/usr/bin/env sh
# the scripting-addition must be loaded manually if
# you are running yabai on macOS Big Sur. Uncomment
# the following line to have the injection performed
# when the config is executed during startup.
#
# for this to work you must configure sudo such that
# it will be able to run the command without password
#
# see this wiki page for information:
# - https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)
#
# sudo yabai --load-sa
# yabai -m signal --add event=dock_did_restart action="sudo yabai --load-sa"
#!/usr/bin/env sh
# bar settings
yabai -m config top_padding 0
# global settings
yabai -m config mouse_follows_focus off
yabai -m config focus_follows_mouse autofocus
yabai -m config window_placement second_child
yabai -m config window_topmost off
yabai -m config window_opacity off
yabai -m config window_opacity_duration 0.0
yabai -m config window_shadow on
yabai -m config active_window_opacity 1.0
yabai -m config normal_window_opacity 0.90
yabai -m config split_ratio 0.50
yabai -m config auto_balance off
# Mouse support
yabai -m config mouse_modifier alt
yabai -m config mouse_action1 move
yabai -m config mouse_action2 resize
# general space settings
yabai -m config layout bsp
yabai -m config bottom_padding 0
yabai -m config left_padding 0
yabai -m config right_padding 0
yabai -m config window_gap 0
# float system preferences
yabai -m rule --add app='^System Information$' manage=off
yabai -m rule --add app='^System Preferences$' manage=off
yabai -m rule --add title='Preferences$' manage=off
# float settings windows
yabai -m rule --add title='Settings$' manage=off
# Some Goland settings, in case you are using it. float Goland Preference panes
yabai -m rule --add app='Goland IDEA' title='^$' manage=off
yabai -m rule --add app='Goland IDEA' title='Project Structure' manage=off
yabai -m rule --add app='Goland IDEA' title='Preferences' manage=off
yabai -m rule --add app='Goland IDEA' title='Edit configuration' manage=off
echo "yabai configuration loaded.."
A few things worth pointing out:
Focus follows mouse — hover over a window and it gets focus, no clicking:
yabai -m config mouse_follows_focus off
yabai -m config focus_follows_mouse autofocus
Zero gaps between windows. I want every pixel. You can bump these up if you prefer some breathing room:
yabai -m config layout bsp
yabai -m config bottom_padding 0
yabai -m config left_padding 0
yabai -m config right_padding 0
yabai -m config window_gap 0
Some apps don’t play well with tiling, so I float them. Remove the Goland rules if you don’t use it:
yabai -m rule --add app='^System Information$' manage=off
yabai -m rule --add app='^System Preferences$' manage=off
yabai -m rule --add title='Preferences$' manage=off
yabai -m rule --add title='Settings$' manage=off
yabai -m rule --add app='Goland IDEA' title='^$' manage=off
yabai -m rule --add app='Goland IDEA' title='Project Structure' manage=off
yabai -m rule --add app='Goland IDEA' title='Preferences' manage=off
yabai -m rule --add app='Goland IDEA' title='Edit configuration' manage=off
Make the config executable:
chmod +x ~/.yabairc
Optional: disable window animations. There’s still a short animation when new windows appear, but you can kill most of the others:
# Disable animations when opening and closing windows.
defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool false
# Accelerated playback when adjusting the window size.
defaults write NSGlobalDomain NSWindowResizeTime -float 0.001
More options in this StackExchange thread.
Restart yabai to pick up the config:
brew services restart yabai
For keybindings, pair yabai with skhd — a hotkey daemon for macOS. It hotloads its config, so you can edit bindings without restarting.
brew install koekeishiya/formulae/skhd
brew services start skhd
Grant accessibility permissions when prompted, then create the config:
touch ~/.skhdrc
Here are the bindings I use. They’re close to my old i3 setup.
Navigate between windows with arrow keys or h,j,k,l:
# change focus
alt - h : yabai -m window --focus west
alt - j : yabai -m window --focus south
alt - k : yabai -m window --focus north
alt - l : yabai -m window --focus east
# (alt) change focus (using arrow keys)
alt - left : yabai -m window --focus west
alt - down : yabai -m window --focus south
alt - up : yabai -m window --focus north
alt - right : yabai -m window --focus east
Move windows around with alt + shift. If a window hits the screen edge, it moves to the next monitor:
# shift window in current workspace
alt + shift - h : yabai -m window --swap west || $(yabai -m window --display west; yabai -m display --focus west)
alt + shift - j : yabai -m window --swap south || $(yabai -m window --display south; yabai -m display --focus south)
alt + shift - k : yabai -m window --swap north || $(yabai -m window --display north; yabai -m display --focus north)
alt + shift - l : yabai -m window --swap east || $(yabai -m window --display east; yabai -m display --focus east)
# alternatively, use the arrow keys
alt + shift - left : yabai -m window --swap west || $(yabai -m window --display west; yabai -m display --focus west)
alt + shift - down : yabai -m window --swap south || $(yabai -m window --display south; yabai -m display --focus south)
alt + shift - up : yabai -m window --swap north || $(yabai -m window --display north; yabai -m display --focus north)
alt + shift - right : yabai -m window --swap east || $(yabai -m window --display east; yabai -m display --focus east)
Set where the next window should appear relative to the focused one with alt + ctrl:
# set insertion point in focused container
alt + ctrl - h : yabai -m window --insert west
alt + ctrl - j : yabai -m window --insert south
alt + ctrl - k : yabai -m window --insert north
alt + ctrl - l : yabai -m window --insert east
# (alt) set insertion point in focused container using arrows
alt + ctrl - left : yabai -m window --insert west
alt + ctrl - down : yabai -m window --insert south
alt + ctrl - up : yabai -m window --insert north
alt + ctrl - right : yabai -m window --insert east
Quick workspace switching with alt + b (like back_and_forth in i3):
# go back to previous workspace (kind of like back_and_forth in i3)
alt - b : yabai -m space --focus recent
# move focused window to previous workspace
alt + shift - b : yabai -m window --space recent; \
yabai -m space --focus recent
Move a window to workspace 1-9 with alt + shift + n:
# move focused window to next/prev workspace
alt + shift - 1 : yabai -m window --space 1
alt + shift - 2 : yabai -m window --space 2
alt + shift - 3 : yabai -m window --space 3
alt + shift - 4 : yabai -m window --space 4
alt + shift - 5 : yabai -m window --space 5
alt + shift - 6 : yabai -m window --space 6
alt + shift - 7 : yabai -m window --space 7
alt + shift - 8 : yabai -m window --space 8
alt + shift - 9 : yabai -m window --space 9
#alt + shift - 0 : yabai -m window --space 10
Mirror and rebalance the layout:
# # mirror tree y-axis
alt + shift - y : yabai -m space --mirror y-axis
# # mirror tree x-axis
alt + shift - x : yabai -m space --mirror x-axis
# balance size of windows
alt + shift - 0 : yabai -m space --balance
Switch between tiling modes:
# change layout of desktop
alt - e : yabai -m space --layout bsp
alt - l : yabai -m space --layout float
alt - s : yabai -m space --layout stack
In stack mode, new windows open on top of each other. Cycle through them with alt + p / alt + n:
# cycle through stack windows
# alt - p : yabai -m window --focus stack.next || yabai -m window --focus south
# alt - n : yabai -m window --focus stack.prev || yabai -m window --focus north
# forwards
alt - p : yabai -m query --spaces --space \
| jq -re ".index" \
| xargs -I{} yabai -m query --windows --space {} \
| jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.x, .id) | reverse | nth(index(map(select(.focused == 1))) - 1).id" \
| xargs -I{} yabai -m window --focus {}
# backwards
alt - n : yabai -m query --spaces --space \
| jq -re ".index" \
| xargs -I{} yabai -m query --windows --space {} \
| jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.y, .id) | nth(index(map(select(.focused == 1))) - 1).id" \
| xargs -I{} yabai -m window --focus {}
Close a window with alt + w:
# close focused window
alt - w : yabai -m window --close
Fullscreen with alt + f. Handy for showing code to someone or during presentations:
# enter fullscreen mode for the focused container
alt - f : yabai -m window --toggle zoom-fullscreen
# toggle window native fullscreen
alt + shift - f : yabai -m window --toggle native-fullscreen
That’s it for yabai + skhd.
macOS settings
I miss dmenu and rofi, but Spotlight is good enough. Rebind it to alt + d in system preferences so it’s always one shortcut away:
You can also bind alt + 1-9 to jump directly to specific workspaces, which I’d recommend:
Hide the menu bar and dock in system settings too.
spacebar (i3status replacement)
Spacebar is a status bar for macOS, similar to i3status. It shows the time, user, battery, and active workspaces.
brew install cmacrae/formulae/spacebar
brew services start spacebar
Grant accessibility permissions again. Spacebar uses Font Awesome for icons:
brew install homebrew/cask-fonts/font-fontawesome
Create the config:
mkdir -p ~/.config/spacebar
touch ~/.config/spacebar/spacebarrc
chmod +x ~/.config/spacebar/spacebarrc
My spacebarrc:
#!/usr/bin/env sh
# Fixes a bug on the newest MacOS version, Thanks to Kamen Vakavchiev!
# see: https://github.com/cmacrae/spacebar/issues/101#issuecomment-1083252227
spacebar -m config right_shell off
spacebar -m config position top
spacebar -m config height 26
spacebar -m config title on
spacebar -m config spaces on
spacebar -m config clock on
spacebar -m config power on
spacebar -m config padding_left 20
spacebar -m config padding_right 20
spacebar -m config spacing_left 25
spacebar -m config spacing_right 15
spacebar -m config text_font "Helvetica Neue:Bold:12.0"
spacebar -m config icon_font "Font Awesome 5 Free:Solid:12.0"
spacebar -m config background_color 0xff202020
spacebar -m config foreground_color 0xffa8a8a8
spacebar -m config power_icon_color 0xffcd950c
spacebar -m config battery_icon_color 0xffd75f5f
spacebar -m config dnd_icon_color 0xffa8a8a8
spacebar -m config clock_icon_color 0xffa8a8a8
spacebar -m config power_icon_strip
spacebar -m config space_icon •
spacebar -m config space_icon_color 0xffffab91
spacebar -m config space_icon_color_secondary 0xff78c4d4
spacebar -m config space_icon_color_tertiary 0xfffff9b0
spacebar -m config space_icon_strip 1 2 3 4 5 6 7 8 9 10
spacebar -m config clock_icon
spacebar -m config dnd_icon
spacebar -m config clock_format "%d/%m/%y %R"
spacebar -m config right_shell on
spacebar -m config right_shell_icon
spacebar -m config right_shell_command "whoami"
echo "spacebar configuration loaded.."
Tell yabai to leave room for the bar by adding this to .yabairc:
# spacebar padding on top screen
SPACEBAR_HEIGHT=$(spacebar -m config height)
yabai -m config external_bar all:$SPACEBAR_HEIGHT:0
Restart spacebar:
brew services restart spacebar
Terminals
I tried both kitty and alacritty and stuck with alacritty. Both work fine — kitty has more built-in features (image support, side-by-side diff, unicode input), alacritty is more minimal and delegates to other tools.
Kitty
brew install --cask kitty
Grant accessibility permissions, then add to .skhdrc:
# open terminal
alt - return : open -n /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d
Alacritty
brew install --cask alacritty
Grant accessibility permissions, then add to .skhdrc:
# open terminal
alt - return : open -n /Applications/Alacritty.app
Cheatsheet
alt + arrow keys— move focus (orh,j,k,l)alt + shift + arrow keys— move window (orh,j,k,l)alt + shift + n— send window to workspace nalt + ctrl + arrow keys— set insertion point (orh,j,k,l)alt + return— open terminalalt + w— close windowalt + f— fullscreenalt + d— Spotlight
Modes:
alt + e— tiling (bsp)alt + s— stacking
In stack mode:
alt + p— next windowalt + n— previous window
Wrapping up
I still prefer Linux, but this setup makes macOS workable for me. yabai + skhd cover most of what i3 does. I miss a few things —> proper window stacking, no animation when new tiles appear but it’s close enough.
My .yabairc, skhdrc, and spacebarrc files are here.