Mosaico: The macOS Tiling Window Manager I Built So I Could Stop Restarting Yabai

Mosaico - tiling window manager for macOS in a single app
Your windows organize themselves. You get to focus on the work.
Matteo 10 min

There is a very specific moment when a side project is born: it is when you type the same command for the fifteenth time in a single day and a little voice in your head says, “you know, you could also just write this thing yourself.”

My command was yabai --restart-service.

I have been using tiling for years: windows arrange themselves into a mosaic on the screen, with no overlap, and you drive everything from the keyboard without ever touching the mouse. On macOS, the de facto standard is yabai, usually paired with skhd for shortcuts and, in my case, a SwiftBar plugin to keep the commands in the menu bar. Three tools, two config files, one small ecosystem I had spent years carefully carving into shape.

Then I installed the macOS 27 beta. And the ecosystem started creaking: yabai losing windows, layouts getting scrambled after waking from sleep, and the daily ritual of restarting the service just to get things back into place. Not yabai’s fault, to be clear - chasing Apple’s betas is a thankless job and the maintainers work miracles. But I use tiling to work, and a work tool you have to keep restarting stops being a work tool and turns into a toy.

Said and done. Again. Welcome to Mosaico.

What Mosaico Is

Mosaico is a tiling window manager for macOS wrapped in a single app. No Homebrew formulas, no config files to edit, no terminal: drag the app into Applications, grant one permission (Accessibility), and your windows start organizing themselves. Everything else - gaps, shortcuts, exclusions - is configured through a graphical interface.

In practice, it replaces the whole yabai + skhd + menu bar plugin trio with a single app that lives next to the clock, using the ⊞ icon.

For anyone who has never used a tiling window manager, the simplest analogy is this: imagine the screen as a tray and the windows as tiles in a mosaic - hence the name, yes, I know, wildly imaginative. Every time you open a new window, the tray automatically gets split again so every tile has its place, with no gaps and no overlap. You do not drag things around anymore: at most, you say “swap this tile with that one.”

What It Does (The Shopping List)

  • Automatic BSP tiling - windows split the screen as a binary tree; a new window splits the focused one (spiral layout), with configurable gaps and padding
  • Native Spaces integration - one independent tiling tree for each Mission Control space; the layout is applied only to the visible space
  • Drop zones with visual overlay - drag a window over another one: drop in the center to swap them, on the top or bottom half to stack vertically, on the left or right half to place them side by side. The target area lights up while you drag
  • Manual edge-aware resizing - drag any edge and the neighboring windows on that side absorb the space, exactly like yabai does
  • Global hotkeys - vim-flavored presets (focus/swap/warp with H/J/K/L), fully remappable from the settings window
  • Mouse support - the pointer follows focus (optional); Option+drag moves a window, Option+right-click resizes it; move a window to another space with Shift+Option+1...7
  • Smart rules - dialogs, sheets, picture-in-picture windows, and non-resizable windows float automatically and stay above the layout; you can exclude whole apps or individual windows directly from the menu bar menu
  • GUI settings - gaps, shortcut recorder, exclusions; everything persisted in JSON. Launch at login (new in 0.2.0) is a one-click toggle directly from the menu bar, and the state is read from the system: it always reflects reality, even if you change it from System Settings. If macOS requires approval for the login item, the settings tell you and take you to the right panel

The default shortcuts are the same ones I used with skhd, so my muscle memory did not have to start from scratch:

ShortcutAction
⌥ H/J/K/LFocus window west/south/north/east
⇧⌥ H/J/K/LSwap windows
⌃⌥ H/J/K/LWarp (move + new split)
⇧⌥ RRotate the layout
⇧⌥ TToggle float
⇧⌥ MMaximize / restore
⇧⌥ ERebalance all windows
⇧⌥ 1...7Move the window to Space N
⌃⌥ QPause / resume tiling
⌃⌥ RRe-tile everything

How It Works Under the Hood (And Why You Do Not Need to Touch SIP)

This was the part I cared most about getting right architecturally. Mosaico is built on top of the public Accessibility API (AXUIElement) to read and move windows, plus two well-known SPIs in this space: _AXUIElementGetWindow for stable window identification and the CGSCopyManagedDisplaySpaces family for native Spaces awareness. These are the same interfaces used by tools like yabai and AltTab.

The important difference is that Mosaico does not require you to disable SIP and does not inject a scripting addition into the system. System Integrity Protection is, to oversimplify a bit, macOS’s bouncer: it keeps people away from the delicate parts of the system. Yabai, for some of its more advanced features, asks you to loosen up the bouncer. Mosaico does not: it stays a polite guest and only uses the doors Apple leaves open.

Even moving a window to another Space uses an elegant trick - which is also the same one yabai uses when it runs without scripting addition: it simulates grabbing the title bar while Mission Control performs the native Control+N space switch, and the window gets carried across with the move. That is why Mission Control’s Control+1...7 shortcuts need to be enabled in macOS keyboard settings.

Installation

You need macOS 14 or later (Apple Silicon or Intel). Then:

  1. Download Mosaico.zip from the releases page and unzip it
  2. Drag Mosaico.app into Applications
  3. First launch: right click -> Open -> Open (the build is not notarized yet)
  4. Grant the Accessibility permission when prompted

That is it. The ⊞ icon appears in the menu bar and tiling starts immediately.

If you want to build it from source instead, Apple’s Command Line Tools are enough - the project has no third-party dependencies, which is something I am moderately proud of:

swift build                       # debug build
.build/debug/Mosaico --selftest   # layout engine self-test
.build/debug/Mosaico --diag       # permission and discovery diagnostics
Scripts/make-app.sh               # produces dist/Mosaico.app and dist/Mosaico.zip

The Certificate Problem (Or: Why macOS Kept Asking Me for Permission)

And here we get to the part that cost me more hair than I had budgeted for, and that deserves a proper explanation because it is exactly the kind of annoyance that hits anyone building macOS apps that rely on Accessibility.

The key point is this: macOS ties Accessibility permission to the app’s code signature. Not to the app name, not to the path: to the signature. It is like that bouncer from earlier does not recognize people by their face, but by the stamp on their hand.

When you build without a signing identity, macOS applies an ad-hoc signature: a disposable stamp, different on every build. Result: rebuild the app, the signature changes, macOS no longer recognizes it, and silently invalidates Accessibility permission. Silently is the key word here: no warning, no popup. The app launches, everything looks fine, and the windows just do not move. During development, when you rebuild dozens of times a day, that means making a pilgrimage to System Settings -> Privacy & Security -> Accessibility after every single build. It is funny the third time. Less funny the thirtieth.

The fix is: a self-signed certificate with a stable name. If the signature never changes, macOS keeps recognizing the app and the permission survives rebuilds. The Scripts/make-app.sh script is already set up for this: if it finds a certificate called “Mosaico Dev” in the keychain, it signs with that; otherwise it falls back to ad-hoc signing.

You only need to create the certificate once, in three steps:

# 1. Generate a self-signed certificate for code signing (valid for 10 years)
openssl req -x509 -newkey rsa:2048 -keyout mosaico-key.pem -out mosaico-cert.pem \
  -days 3650 -nodes -subj "/CN=Mosaico Dev" \
  -addext "keyUsage=digitalSignature" \
  -addext "extendedKeyUsage=codeSigning" \
  -addext "basicConstraints=CA:false"

# 2. Import key and certificate into the login keychain
security import mosaico-key.pem -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign
security import mosaico-cert.pem -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign

# 3. Mark the certificate as trusted for code signing (macOS will ask for your password)
security add-trusted-cert -p codeSign -k ~/Library/Keychains/login.keychain-db mosaico-cert.pem

Let us go through what each step does, without hand-waving:

  1. openssl req -x509 generates a private key + certificate pair. The important detail is -subj "/CN=Mosaico Dev" - the name the script will look for in the keychain - plus the extendedKeyUsage=codeSigning extension, which tells the system this certificate is meant to sign code and not, say, a website. Ten years of validity: by the time it expires, I will most likely be rewriting everything in a language that does not exist yet.
  2. security import ... -T /usr/bin/codesign drops the key and certificate into the login keychain. The clever part is the -T flag: it explicitly authorizes the codesign binary to use that key, so you do not end up with a keychain popup on every build.
  3. security add-trusted-cert -p codeSign tells macOS “trust this certificate, but only for code signing.” Without this step, the certificate would exist, but the system would still treat it like a complete stranger.

To verify everything worked:

security find-identity -p codesigning

If "Mosaico Dev" appears in the list, you are good. From that point on, every rebuild keeps the same signature and the Accessibility permission survives. On the first signed build, macOS may show a keychain prompt for codesign: choose “Always Allow” and you will not see it again.

Two warnings are worth spelling out:

  • The generated .pem files must not end up in Git (the repo’s .gitignore already excludes them, but still: that private key is yours and nobody else’s)
  • A self-signed certificate solves the problem only on your machine. If you distribute the app to other people, Gatekeeper will keep giving you the side-eye - for that you need an Apple Developer ID with notarization, which is why it is at the top of the roadmap

Where Things Stand

Mosaico is still in early development - while I am writing this, the latest public release is v0.2.0 - but it is already my daily driver: the core pieces - tiling, native Spaces handling, hotkeys, drop zones, and settings - are stable. Expect a few rough edges around less common window types and multi-display setups.

And here comes a confession: the 0.1.x changelog is basically a war diary against macOS window management edge cases. Sleep breaking the layout on wake (the Accessibility API briefly reports windows as invalid after wake, and the model was pruning them before the wake notification arrived). Terminals that “snap back” to the character grid and triggered an infinite apply -> bounce -> adopt loop. The “ghost tiles” left in the layout by windows hidden with Command+H, which Accessibility still reported as valid. Every one of these bugs deserves a post of its own; for now I will spare you and leave you with the CHANGELOG, which is more honest than any marketing page could ever be.

Known limitations, in the interest of transparency:

  • The distributed build is self-signed: on first launch you need right click -> Open, and if the signing identity changes macOS asks for Accessibility permission again
  • Native fullscreen windows stay out of tiling until they exit fullscreen
  • The Accessibility API cannot enumerate windows on inactive Spaces, so the menu bar list fills in gradually as you visit each Space
  • The interface is currently only available in Italian (localization is on the roadmap - yes, for once the English-speaking world will have to adapt)

Roadmap

  • Developer ID signing + notarization (goodbye Gatekeeper friction and permission re-prompts)
  • Homebrew cask
  • English UI localization
  • Better multi-display support
  • Auto-float rules configurable from the GUI
  • Optional window borders / focus highlighting
  • Layout presets (columns, monocle) alongside BSP

The project is open source under the MIT license on GitHub. Issues and PRs are welcome - especially from people with weird multi-display setups, which is exactly the territory where the dragons live.


P.S. After PlayNotch I swore the Swift chapter was closed. Then the macOS 27 beta broke yabai and that oath lasted about as long as a good New Year’s resolution. At this point I may as well admit it: the notch, the windows… I am slowly rewriting macOS one piece at a time. Please send help!

Copiato