Using i3-like Tiling Window Managers in MacOS with yabai.

Anuj Chandra
7 min readFeb 3, 2021

--

I have been a Linux user for almost 7 years before I switched to the MacOS to improve stability and better software support (more specifically MS Office, yea I blame you Microsoft). Moving across different Operating Systems means finding an alternative for every piece of software that was part of my configuration. One of the most difficult tasks was finding a replacement of i3 since the underlying architecture is completely different.

Upon wasting endless hours, I came across three different alternatives, given below in the order of difficulty in integrating.

  1. Veeer (https://veeer.io/) and Tiles (https://freemacsoft.net/tiles/), made for people who want basic window management, but nowhere close to the functionality provided by i3.
  2. Amethyst (https://github.com/ianyh/Amethyst): I used this for a while, and though it does provide some of the features of i3, it lacks customizability, and there’s no option to bound applications to spaces.
  3. yabai (https://github.com/koekeishiya/yabai): Although yabai is based on bspwm, a tiling window manager based on binary space partitioning, it checks off a lot of boxes for me and works really well.

BTW, I managed to create my desktop which looks like the image below.

Installation

I am assuming people have Homebrew pre-installed on their machines before actually reading more into this article. If not, install it from the official page of Homebrew (https://brew.sh). However, if you want to use MacPorts or nix-env, it is possible to build the application locally for installation.

yabai itself is just a window manager and modifies your window layout using a binary space partitioning algorithm. To control yabai, you also need to install skhd to communicate with the yabai service.

The following commands install yabai and skhd:

# Install skhd
brew install koekeishiya/formulae/skhd
# Start skhd
brew services start skhd
# Install yabai
brew install koekeishiya/formulae/yabai
# Start yabai
brew services start yabai

Once you have installed the above software, you’ll need to give accessibility permissions to them (see the reference screenshot below).

yabai can manipulate the windows through the Dock.appThey can only do some basic tasks like sending windows to spaces and modifying window properties with the default setting.

To unlock the full potential of yabai, it needs to inject a custom script into the Dock.app to actually manipulate windows and spaces. To do so, you will need to disable the System Integrity Protection of your Mac, security feature of macOS first introduced in 10.13 and protects some files and directories from being modified — even by the root user. To understand the implications of disabling SIP, you can read more about it here,. You can disable it by booting your machine in Recovery mode and executing the following commands on Terminal.

# If you're on macOS 11.0.1
# Requires Filesystem Protections and Debugging Restrictions to be disabled (workaround because --without debug does not work)
# (printed warning can be safely ignored)
csrutil disable --with kext --with dtrace --with nvram --with basesystem

# If you're on macOS 10.14 and 10.15
# Requires Filesystem Protections and Debugging Restrictions to be disabled
# (printed warning can be safely ignored)
csrutil enable --without debug --without fs

# If you're on macOS 10.13
# (disables SIP completely)
csrutil disable

Configure yabai

Okay, so now we are getting somewhere. Let’s look at the configuration files generated by yabai and skhd. The default configuration for both sits in the home directory. All the configurations on how you want your windows to behave goto your .yabairc . Let’s first look at how yabai is configured.

Borders and Padding

You need to specify layout as bsp . This is what tells yabai to balance windows using its binary space partitioning algorithm. The rest of the configuration provide the gaps between windows and desktop borders.

yabai -m config layout                       bsp
yabai -m config top_padding 3
yabai -m config bottom_padding 3
yabai -m config left_padding 3
yabai -m config right_padding 3
yabai -m config window_gap 10

Application Specific Rules

I usually like my browsers to stay in Space 1, Code editor (VS Code and Vim) in Space 2, IDEs and non-active projects in Space 3, Terminal in Space 4, and so on. yabai makes this configuration possible as follows:

# Web browsers
yabai -m rule --add app="^Google Chrome|Firefox$" space=1
# Text editors
yabai -m rule --add app="^(Code|RStudio)$" space=2
# Jetbrains apps
apps="^(IntelliJ IDEA|WebStorm|RubyMine|PyCharm|DataGrip)$"
yabai -m rule --add app="${apps}" space=3 manage=off
# Terminal
yabai -m rule --add app="^(Alacritty)$" space=4
# Music
yabai -m rule --add app="^(Spotify|Amazon Music)$" space=5
# Social
yabai -m rule --add app="^(Slack|WhatsApp|Microsoft Teams)$" space=6

Status Bar

yabai initially supported a status-bar similar to i3, but dropped it in version 3.0.0 in issue#486. Now, there are two alternatives:

  1. spacebar: Extracted spacebar of yabai as a separate application. Easiest to set up and faster since it’s a direct descendent of yabai.
  2. simple-bar: Built on top of Übersicht, runs on Javascript and React. Easier to customize and provides lots of features. There’s a noticeable latency for the widget to update when the screen is updated since it relies on yabai events.

I have tried both the status bars and there’s no clear winner among them. Sure simple-bar is more customizable but it’s also slower. I’ve been using yabai bar/spacebar for close to 3 years but switched recently to spacebar for its powerful features.

Configure skhd

skhd allows you to create keybindings that can manipulate yabai. The configuration (keyboard shortcuts) sit in .skhdrc in your home folder default.

Application Shortcuts

# Reload yabai
ctrl + alt + cmd - r : launchctl kickstart -k "gui/${UID}/homebrew.mxcl.yabai"
# Application Shortcuts
ctrl + fn - f : open ~
ctrl + fn - t : open -na /Applications/Alacritty.app
ctrl + fn - s : open "x-apple.systempreferences:"
ctrl + fn - a : open -a "About This Mac"
ctrl + fn - c : open -na /Applications/Google\ Chrome.app
ctrl + fn - v : open -na /Applications/Visual\ Studio\ Code.app
ctrl + fn - l : open -na /Applications/Slack.app
ctrl + fn - m : open -na /Applications/Spotify.app
ctrl + fn - g : open -na /Applications/Signal.app

The first command reloads yabai. This can also be done using brew services restart yabai but it takes too much time to execute. Reloading the service directly is faster.

Full Screen using stack mode

yabai 3.3.0 added the support to stack windows in the same region. This is similar to i3’s fullscreen mode and allows for an unintrusive mode of writing code. I don’t use Mac OS’s native fullscreen because it lives in its separate space making it difficult to track applications when they are all in full-screen mode.

In order to switch between bsp and stack mode, I made a nifty little key-binding.

ctrl + shift - space : yabai -m space --layout "$(yabai -m query --spaces --space | jq -r 'if .type == "bsp" then "stack" else "bsp" end')"

Switch Applications

While inside a screen, you can switch between applications in bsp mode using the below keybinding

# focus window in bsp mode
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

In stack mode, applications can be switched using the given keybinding

alt + p: yabai -m window --focus stack.prev
alt - n: yabai -m window --focus stack.next
alt - f: yabai -m window --focus stack.first
alt - g: yabai -m window --focus stack.last

While this is the default, I don’t prefer it since now I need to remember two sets of keybindings to cycle between windows. Instead dominiklohmann suggested an alternative keybind to cycle between both bsp and stacked windows.

# forward
ctrl + shift - right : 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 {}

# backward
ctrl + shift - left: 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 {}

Switching spaces

This is thankfully one feature Mac OS provides inbuilt in its System Preferences. So you can use keybindings to let let yabai do it for you.

# fast focus desktop
ctrl - left : yabai -m space --focus prev
ctrl - right : yabai -m space --focus next
ctrl - z : yabai -m space --focus recent
ctrl - 1 : yabai -m space --focus 1
ctrl - 2 : yabai -m space --focus 2
ctrl - 3 : yabai -m space --focus 3
ctrl - 4 : yabai -m space --focus 4
ctrl - 5 : yabai -m space --focus 5
ctrl - 6 : yabai -m space --focus 6
ctrl - 7 : yabai -m space --focus 7
ctrl - 8 : yabai -m space --focus 8
ctrl - 9 : yabai -m space --focus 9
ctrl - 0 : yabai -m space --focus 10

Move windows to spaces

The below keybindings are useful when I want to send a window to another space similar to i3.

ctrl + shift - 1 : yabai -m window --space  1; yabai -m space --focus 1
ctrl + shift - 2 : yabai -m window --space 2; yabai -m space --focus 2
ctrl + shift - 3 : yabai -m window --space 3; yabai -m space --focus 3
ctrl + shift - 4 : yabai -m window --space 4; yabai -m space --focus 4
ctrl + shift - 5 : yabai -m window --space 5; yabai -m space --focus 5
ctrl + shift - 6 : yabai -m window --space 6; yabai -m space --focus 6
ctrl + shift - 7 : yabai -m window --space 7; yabai -m space --focus 7
ctrl + shift - 8 : yabai -m window --space 8; yabai -m space --focus 8
ctrl + shift - 9 : yabai -m window --space 9; yabai -m space --focus 9
ctrl + shift - 0 : yabai -m window --space 10; yabai -m space --focus 10

Conclusion

yabai also supports lots of other features like multiple-monitors and events-driven actions. It’s an awesome tool which requires some setup but once done, can make your life 10x easier. I hope this intro may be useful for some of the folks who are looking into tiling managers for MacOS.

PS: My configuration for yabai is here and skhd is here.

--

--