Shaka

New in 1.2.0Workspaces, mouse tiling and a cheat sheet

Float or tile. One key between them.

Shaka is a hand in your menu bar and a handful of keys under your fingers. Flow nudges windows around on a spring and enforces nothing. Reef packs every one of them into a tiling layout with no overlap, across nine workspaces per display. ⌃ / swaps between them, and coming back puts every window exactly where it was.

$curl -fsSL https://raw.githubusercontent.com/wes/shaka/main/install.sh | bash

Free · MIT · macOS 13+ · Xcode Command Line Tools

FinderFileEditViewGoWindowHelp🤙100%Thu 9:41
Safari
‹ ›
shaka — swift build

~/Sites/shaka swift build -c release

[9/9] Compiling Shaka WindowManager.swift

Build complete! (12.84s)

🤙 Shaka Window Manager

─────────────────────────

ctrl + arrows          focus

ctrl + opt + arrows   move

ctrl + cmd + arrows   snap

~/Sites/shaka ▌

Notes
Board meeting
Groceries
Shaka — todo
Rincon trip
Music
▶︎
Click the screen, then use the arrow keys — or the shortcuts below
⌃ / switches
Focus
⌃ + arrows
Nearest window that way
Move
⌃⌥ + arrows
Nudge 80 points
Resize
⌃⇧ + arrows
80 points, from the centre
Snap
⌃⌘ + arrows
½, then ⅓, then ⅔
waiting for a key

That desktop is drawn, not screenshotted, and the windows are not on CSS transitions. Every move, resize and snap runs Shaka's own geometry, and switching it to Reef runs Shaka's own dwindle tree — the same recursion, the same gaps — integrated by Shaka's own spring at the same fixed 1/120 step the app uses. The overshoot you feel here is the overshoot you get at animation_damping = 28.

Two modes

Same keys. Opposite philosophies.

Most people want a tiler right up until they want to drag a window somewhere silly. Shaka stopped arguing about it and shipped both, on one set of bindings, with a single keystroke in between — and the menu bar tells you which one you are in from across the room.

🤙

Flow

Free-floating

Windows go where you put them. Nudge, resize, centre, fill and snap, on a spring, with nothing enforced and nothing to lay out.

👊

Reef

Dwindle tiling, nine workspaces

Every window becomes a tile in a binary tree that fills the screen. Nothing overlaps, nine workspaces per display hold nine of those trees, and one keystroke lifts it all back off.

One column of keys, two columns of meaning.

⌃⌥ ← nudges a window in Flow and swaps two tiles in Reef. Nothing to relearn when you switch — the verb changes, the finger does not.

Reef is borrowed, not invented.

It is Hyprland's dwindle layout, down to the names: togglesplit, resizeactive, gaps_in and gaps_out. If you have used one on Linux, you already know this one.

It is about 4,100 lines of Swift.

Cocoa, the Accessibility API, a binary tree and a spring, with no third-party packages anywhere in Package.swift. You build it yourself, which means you can read all of it first.

Reef 👊

A tiling manager you can put back down.

Entering Reef takes every tileable window on every display, writes down where it was, and packs it into a binary tree that fills the screen exactly. Each new window splits the focused tile in half along that tile's long axis, so the layout spirals inward the way dwindle does and the halves stay near-square instead of degenerating into columns.

Press ⌃ / again and every window goes back to the frame Reef found it in, on the same spring. That is the whole pitch: the structure is something you drop onto your screen for an afternoon and lift off again, not something you commit your desktop to.

A
01

One window fills the screen.

A
B
02

A is wider than tall, so it splits side by side.

A
B
C
03

B is now taller than wide, so C stacks under it.

A
B
C
D
04

C is wide again — D takes its right half.

Nobody chose those four rectangles. Each one is what host.rect.width >= host.rect.height decided when the window opened, which is why the shape is predictable even though you never picked a layout.

Entering records everything

Every tileable window on every display has its frame written down before a single tile is laid, which is what makes leaving lossless rather than approximate.

The tree is per display, per workspace

Each screen gets nine dwindle trees and shows one of them. Run out of tiles pressing a direction and focus carries on onto the next display.

It reconciles, it does not poll

An Accessibility observer per app watches windows appear, close, minimise and come back; a five-second timer covers apps that report nothing. Bursts are coalesced into one diff.

Leaving puts it all back

Every saved frame is handed back to its window on the same spring, parked workspaces included, so Reef is something you drop onto the screen and lift off again — not something you commit to.

The keys Flow does not have

Reef adds the actions a tiler needs and a floating manager has no use for — these, plus the eighteen number keys the workspaces sit on. In Flow their bindings are left unbound rather than made inert — the tap passes the keystroke through to whatever app you are in, so ⌃⇧S stays yours until the moment you want it, and macOS keeps ⌃1 for its own Spaces.

cycle_nextCycle focus forward⌃⌥ ⇥
cycle_prevCycle focus back⌃⌥⇧ ⇥
toggle_splitToggle split direction⌃⇧ S
toggle_floatToggle floating⌃⇧ F
workspace_nextNext workspace⌃⌥ ]
workspace_prevPrevious workspace⌃⌥ [
workspace_recentLast workspace⌃ `
2×6

The gaps add up right.

gaps_out = 12 at the screen edge and gaps_in = 6 around each tile, so two neighbours sit 12 points apart — the same arithmetic Hyprland does. Inset the screen by gaps_out − gaps_in before splitting, inset each leaf by gaps_in after, and the outer margin and the gutters come out equal without a special case.

If you are coming from Hyprland

The dispatcher each Reef action is a reading of.

Focus a tilemovefocus

Nearest tile in the direction, scored off the laid-out rects.

Swap tilesmovewindow

The two leaves exchange windows; the tree shape does not move.

Resize the splitresizeactive

Moves the tile's trailing edge, or its leading one when that edge is the screen border.

Flip the splittogglesplit

Turns a side-by-side pair into a stacked one and back.

Float a windowtogglefloating

The tile leaves the tree and gets Flow behaviour at 60% of the screen.

Fullscreenfullscreen

One tile covers the whole visible frame, gaps and all.

Send to a displaymovewindow mon:<dir>

Removed from one tree and inserted into the next display's.

Show a workspaceworkspace N

Nine per display, each a full dwindle tree. The one you left is parked, not closed.

Send to a workspacemovetoworkspacesilent

The window goes; the screen you are looking at does not change.

Back to the last oneworkspace previous

One key bounces between the two you are actually using.

Drag a tilebindm movewindow

Modifier + left-drag. Tiles trade places as the cursor crosses them.

Drag to resizebindm resizewindow

Modifier + right-drag, from whichever corner the cursor started nearest.

Reef 👊 · Workspaces

Nine desks per display.

Every display gets 9 workspaces, and each one is a full dwindle tree of its own — its own windows, its own splits, its own ratios. ⌃ 1 – 9 brings one to the screen. Add ⇧ and the focused window goes there without you following it.

These are Shaka's, not macOS Spaces. They live inside Reef, they leave Mission Control alone, and their keys are unbound in Flow — so if you have macOS's own Switch to Desktop N on ⌃1–9, Flow still hands those to macOS and Reef takes them for itself.

13 windows
22 windows
✓ 34 windows
41 window
5empty
62 windows
7empty
8empty
9empty

The same list the menu bar shows under Workspaces, with how full each one is; picking one there switches to it exactly as its shortcut would. The status item itself reads 👊 {n} — the one thing about the layout you cannot tell by looking at the windows in front of you.

Nine trees, not nine screenshots

Each workspace is a full dwindle tree with its own splits and ratios. Switching to one lays it out again from the tree, so a workspace you left an hour ago comes back the shape you left it.

Parked, never closed

Windows on a hidden workspace are slid below the display and kept at full size, so returning springs them back up into their tiles. Nothing is minimised and nothing is quit — set workspace_hide = "minimize" for apps that refuse to sit off screen.

Sending does not follow

⌃⇧ + a number hands the focused window to another workspace and leaves you where you are. ⌃ + ` `` bounces back to the one you came from, which is the pair of keys you actually wear out.

Cmd-Tab still works

Reach a window parked on another workspace from the app switcher and Shaka switches to that workspace rather than leaving you on a screen that did not change.

⌃ 1 – 9Show that workspace on this display
⌃⇧ 1 – 9Send the focused window there, and stay put
⌃⌥ ] / [Step to the next or previous one
⌃ `Bounce back to the one you came from

Reef 👊 · The mouse

Grab an edge and the layout agrees with you.

Every edge of a tile that is not the screen border is the split line of exactly one node in the tree, so resizing is never approximate: move that edge, the tiles either side of it reflow, and everything else stays where it is. Three gestures reach the same machinery, and so does ⌃⇧ ←.

The last one is the interesting one. Grab a tiled window's own border and Shaka does not intercept the drag at all — the app resizes itself the way it always has, Shaka reads the frame it lands on, and folds the difference back into the split ratios. The window keeps the size you gave it because the layout changed to agree with it.

⌃⌘ dragSwallowed

Throw a tile across the layout

Hyprland's bindm movewindow. Tiles trade places as the cursor crosses them, so what is on screen is a valid tiling at every moment of the drag rather than only at the end.

⌃⌘ right-dragSwallowed

Resize from the nearest corner

The two edges nearest where the drag started ride along with the cursor, each one moving the single split that draws it. Shaka eats the event, so no app underneath ever sees it.

Window borderWatched

Pull the window's own edge

Here Shaka touches nothing: the app resizes itself the way it always does, and Shaka reads the frame it lands on and folds the difference back into the split ratios. The window keeps your size because the layout changed to agree with it.

Title barWatched

Drop one window onto another

Drag a tiled window by its title bar onto a neighbour and the two swap places — the same operation ⌃⌥ + an arrow does, for when your hand is already on the mouse.

Two modifiers, on purpose

A bare ⌃ + click is macOS's right-click, and swallowing it would cost you context menus on every tiled window. Set mouse_modifier = "leader" for the one-modifier Hyprland feel, or "" to turn the gesture off.

Nothing gets squeezed out

No drag can leave a tile smaller than min_tile_size — 120 points by default — whichever of the three you are using, and the clamp is a real size rather than a fixed fraction of the split.

Paced, not spammed

A trackpad emits well over a hundred events a second and every one would otherwise mean an Accessibility write per neighbour. A watched window's frame is read at 25Hz and new frames go out at most every 16ms.

Motion

A window with weight.

Most window managers teleport. Shaka runs a damped spring on all four numbers of the frame at once — origin and size, on a timer at 8ms — so a window leans into the move, arrives, and settles. It is the difference between a window being repositioned and a window going somewhere.

Press a direction twice quickly and it does not stutter: the second press retargets the spring in flight, keeping the velocity it already had, so a held key glides instead of stepping.

Both modes share one animator, which is what makes switching feel like a decision rather than a redraw: every tile springs into place at once, each with its own velocity, off a single timer that drops to 16ms once more than two windows are moving so the Accessibility round trips have room to land.

Click anywhere on the strip to send the window there. Those two lines go in config.toml exactly as they read.

vel += (k·(target − cur) − c·vel)·dt
cur += vel·dt

Eight lines of physics, run over x, y, width and height. When the remaining distance and the remaining speed both fall under half a point, the window is set exactly on target and the timer stops.

Nothing runs when nothing moves

The animation timer is created for a move and cancelled the moment the last window settles. Between keystrokes Flow is an event tap and a menu, and that is all.

Reduced motion is respected here

This page's demo checks prefers-reduced-motion and jumps straight to the target instead of springing.

Flow 🤙 · Snapping

Press it again for a third.

⌃ ⌘ ← puts the window on the left half. Press it again and it is a third; again and it is two thirds; again and you are back to half. Four common layouts live on one key, and changing direction starts the cycle over. In Reef the same key means something else entirely — it hands the tile to the next display.

First press½
Second press⅓
Third press⅔
½

A half is not half the screen.

It is fraction × (width − padding) − padding — the padding around the outside is spoken for first, and the gutter down the middle comes out of the two halves. Snap one window left and another right and the gap between them is the same 10 points as the gap around them, which is the only version of a half that looks right.

Edges are sticky

A nudge that lands within 20 points of an edge gives up the difference and sits at the padding instead — so windows line up without you aiming.

Resizing works from the centre

Grow a window and it spreads both ways rather than walking off to the right. It stops shrinking at 200 points, whichever key you are leaning on.

Fill is not fullscreen

⌃ ⇧ ↩ fills the visible frame minus the padding. No green button, no Space of its own, no animation you have to sit through.

Focus

Arrow keys, but for windows.

⌘-Tab cycles through applications in the order you last used them, which is a different question from the window on the left. Shaka answers the second one: ⌃ ← activates the app that owns the nearest window in that direction and raises it.

Flow reads the on-screen window list in a single call, so it sees every app's windows rather than only its own. Reef does not need to ask: it already has every tile's rectangle, so it scores them straight off the tree — and runs off the edge onto the next display when there is no tile left in that direction.

01

Straight ahead beats close by

Distance is scored with the perpendicular axis weighted four times as heavily, so a window directly left wins over a nearer one off up a corner.

02

Ten points of slack

A neighbour has to be more than ten points away in the direction you pressed, which keeps two windows sharing an edge from swapping back and forth.

03

Only real windows

Standard windows, not minimised, whose position and size the app will actually let you set. Sheets, palettes, popovers and fixed-size dialogs are not windows you were trying to reach.

04

Raise, don't rearrange

Focusing moves nothing. The owning app is activated and the matching window is raised — the geometry is left exactly as it was.

On a real desk

Twenty-five seconds, no narration.

Everything above is drawn. This is not — it is a screen recording of the app moving real windows around a real Mac. It predates Reef, so what you are watching is Flow: nudging, snapping and filling, at the speed you actually use it.

Both modes · ⌃ K

The list, in your own bindings.

A tiler with two modes and 45 bindings needs somewhere to look them up that is not a README. ⌃ K puts every shortcut that does something in the mode you are in on screen — built from the live config, so it stays right the moment after you rebind something. Press it again, hit esc, or click it to put it away.

It is a non-activating panel, so it never takes focus from the window you are about to rearrange, and switching modes while it is up swaps it for the other half rather than closing it. The same list lives in the menu bar under Shortcuts, where every row runs its action when you pick it.

— what ⌃ K puts on screen in each

👊  Reef mode

⌃/ switches modes  ·  ⌃K or esc closes

Focus

⌃ ←Focus tile left
⌃ →Focus tile right
⌃ ↑Focus tile up
⌃ ↓Focus tile down
⌃⌥ ⇥Cycle focus forward
⌃⌥⇧ ⇥Cycle focus back

Move

⌃⌥ ←Swap tile left
⌃⌥ →Swap tile right
⌃⌥ ↑Swap tile up
⌃⌥ ↓Swap tile down
⌃ ↩Promote to master

Resize

⌃⇧ →Widen split
⌃⇧ ←Narrow split
⌃⇧ ↑Heighten split
⌃⇧ ↓Shorten split

Snap & Displays

⌃⌘ ←Send to display left
⌃⌘ →Send to display right
⌃⌘ ↑Send to display up
⌃⌘ ↓Send to display down

Layout

⌃⇧ ↩Toggle fullscreen
⌃⇧ SToggle split direction
⌃⇧ FToggle floating

Workspaces

⌃ 1…9Show workspace 1–9
⌃⇧ 1…9Send window to workspace 1–9
⌃⌥ ]Next workspace
⌃⌥ [Previous workspace
⌃ `Last workspace

Shaka

⌃ /Switch to Flow 🤙
⌃ KShow the shortcut cheat sheet

Both halves of that come out of one catalogue in the source — Shortcuts.swift — which is also what writes the commented config file and builds the menu. An action added there shows up in all four places, and cannot show up in three of them with the wrong key.

The interface

A mode picker and a shrug.

Shaka is an LSUIElement, so it has no dock icon and no window of its own. Everything it can be asked to do is in this menu, and most days you will open it to switch modes with the mouse instead of the keyboard, or to turn the thing off for a screenshare and turn it back on afterwards.

The glyph is the status: 🤙 for Flow, 👊 for Reef — with the workspace number beside it in Reef — and 🤙⚠ when Accessibility has been revoked, at which point the menu drops everything except an explanation and a button that opens the right settings pane.

Flow / Reef
The mode picker, ticked against the one you are in. Same thing ⌃ / does, for when your hand is already on the mouse.
Workspaces
Reef only: all nine, with how many windows each is holding and a tick against the one on screen. Picking one switches to it.
Cheat Sheet
Puts the ⌃ K overlay up, for the times you reach for the menu before you reach for the key.
Shortcuts
Every binding as it is currently configured, described for the mode you are in — and every row runs its action when you pick it, so the menu is a way to use the shortcuts rather than only to read them.
Enabled
Disables the event tap without quitting, handing any tiled windows back first. Your keys go back to whatever they meant before.
Edit Config…
Opens config.toml in TextEdit, because that is a thing every Mac has.
Reload Config
Restores windows, re-reads the file, rebuilds the bindings and the menu. No restart, no relaunch.
Quit Shaka
Untiles, tears the tap down and exits.
🤙Pick a mode — the menu rebuilds, exactly as it does in the app.
Shaka — Flow mode
⌃ / to switch
Cheat Sheet⌃K
Shortcuts▶
✓Enabled⌘E
Edit Config...⌘,
Reload Config⌘R
Quit Shaka⌘Q
Focus
⌃ ←Focus window left
⌃ →Focus window right
⌃ ↑Focus window up
⌃ ↓Focus window down
Move
⌃⌥ ←Nudge window left
⌃⌥ →Nudge window right
⌃⌥ ↑Nudge window up
⌃⌥ ↓Nudge window down
⌃ ↩Center window
Resize
⌃⇧ →Grow width
⌃⇧ ←Shrink width
⌃⇧ ↑Grow height
⌃⇧ ↓Shrink height
Snap & Displays
⌃⌘ ←Snap left ½ ⅓ ⅔
⌃⌘ →Snap right ½ ⅓ ⅔
⌃⌘ ↑Snap top ½ ⅓ ⅔
⌃⌘ ↓Snap bottom ½ ⅓ ⅔
Layout
⌃⇧ ↩Fill screen
Shaka
⌃ /Switch to Reef 👊
⌃ KShow the shortcut cheat sheet

Configuration

One file, written for you on first launch.

~/.config/shaka/config.toml appears the first time Shaka runs, commented, with every default in it and the two modes' settings under their own headings. Change the leader to "opt" and the Mission Control collision disappears; set default_mode = "reef" and it tiles from launch; rebind any action to any key you like; hit Reload Config and it is live.

A binding you leave out falls back to the default rather than disappearing, so a config written before workspaces existed picks up all of their keys on the next launch instead of quietly losing them. Every line carries a comment saying what it does in Flow and then in Reef, which makes the file the same list of actions the menu and the cheat sheet are built from.

# ─────────────────────────────────────────────────────────────────────────────
#  Shaka Window Manager — configuration
#
#  Every option Shaka understands is listed here with its default. Delete a
#  line to go back to that default; anything you leave out keeps working.
#  Apply changes with 🤙 → Reload Config, no restart needed.
# ─────────────────────────────────────────────────────────────────────────────

# The modifier every "leader+..." binding below starts from.
#   "ctrl" | "opt" (= "alt" / "option") | "cmd" (= "command") | "shift"
leader = "ctrl"

# Which mode Shaka starts in.
#   "flow" — free-floating: nudge, resize and snap windows wherever you like
#   "reef" — Hyprland-style dwindle tiling: every window is a tile
default_mode = "flow"


# ── Flow mode ────────────────────────────────────────────────────────────────

# How far one nudge moves a window, in points.
move_step = 80

# How much one resize grows or shrinks a window, in points.
resize_step = 80

# A move that lands within this many points of a screen edge sticks to it.
# Set to 0 to turn edge snapping off.
edge_snap = 20

# Margin kept between a snapped or filled window and the screen edge.
screen_padding = 10


# ── Reef mode ────────────────────────────────────────────────────────────────

# gaps_in:  space around each tile, so neighbours sit 2x this apart
# gaps_out: space at the screen edge
gaps_in = 6
gaps_out = 12

# Smallest tile a resize may leave behind, in points.
min_tile_size = 120

# Workspaces per display, 1-9. Each one keeps its own tiling.
workspaces = 9

# How windows on a hidden workspace are put away:
#   "offscreen" — slid below the display, instant and reversible
#   "minimize"  — sent to the Dock, for apps that refuse to be moved off screen
workspace_hide = "offscreen"


# ── Reef mouse ───────────────────────────────────────────────────────────────

# Hold this and drag inside a tile: left button moves it, right button resizes.
# Two modifiers by default, because a bare ctrl+click is macOS's right-click —
# set it to "leader" for the one-modifier Hyprland feel, or "" to turn the
# gesture off entirely.
mouse_modifier = "leader+cmd"

# Dragging a tiled window's own border reshapes the tiling around it.
#   true | false
mouse_edge_drag = true


# ── Animation (both modes) ───────────────────────────────────────────────────

# Spring the windows travel on. Higher stiffness is snappier; lower damping
# overshoots and bounces. 300 / 28 lands quickly without wobbling.
animation_stiffness = 300
animation_damping = 28


# ── Bindings ─────────────────────────────────────────────────────────────────
#
#  Write a combo as modifiers and one key joined by "+", e.g.
#  "leader+shift+left" or "ctrl+opt+a". "leader" stands in for the leader key
#  set above, so rebinding the leader moves every shortcut at once.
#
#  Modifiers: leader, ctrl, opt (alt), cmd, shift
#  Keys:      left right up down · return space tab escape delete
#             a-z · 0-9 · - = [ ] ; ' , . / ` \
#
#  The comment after each line says what it does in Flow, then in Reef. Actions
#  marked "Reef only" have no Flow equivalent: in Flow their keys are left
#  alone, so they reach the focused app instead.

[bindings]
# --- Focus ---
focus_left = "leader+left"              # Flow: Focus window left  ·  Reef: Focus tile left
cycle_next = "leader+opt+tab"           # Reef only: Cycle focus forward
cycle_prev = "leader+opt+shift+tab"     # Reef only: Cycle focus back

# --- Move ---
move_left = "leader+opt+left"           # Flow: Nudge window left  ·  Reef: Swap tile left
center = "leader+return"                # Flow: Center window  ·  Reef: Promote to master

# --- Resize ---
grow_width = "leader+shift+right"       # Flow: Grow width  ·  Reef: Widen split
shrink_width = "leader+shift+left"      # Flow: Shrink width  ·  Reef: Narrow split

# --- Snap & Displays ---
snap_left = "leader+cmd+left"           # Flow: Snap left ½ ⅓ ⅔  ·  Reef: Send to display left

# --- Layout ---
fill = "leader+shift+return"            # Flow: Fill screen  ·  Reef: Toggle fullscreen
toggle_split = "leader+shift+s"         # Reef only: Toggle split direction
toggle_float = "leader+shift+f"         # Reef only: Toggle floating

# --- Workspaces ---
workspace_1 = "leader+1"                # Reef only: Workspace 1
move_to_workspace_1 = "leader+shift+1"  # Reef only: Send to workspace 1
workspace_next = "leader+opt+]"         # Reef only: Next workspace
workspace_prev = "leader+opt+["         # Reef only: Previous workspace
workspace_recent = "leader+`"           # Reef only: Last workspace

# --- Shaka ---
toggle_mode = "leader+/"                # Flow: Switch to Reef 👊  ·  Reef: Switch to Flow 🤙
show_shortcuts = "leader+k"             # Show the shortcut cheat sheet

The file on disk spells out all four directions and all nine workspaces; this listing collapses the repeats. Everything else is what Shaka writes, comments included.

leaderBoth The modifier every shortcut starts with. "opt", "cmd" and "shift" also work.
default_modeBoth Which mode Shaka starts in. Set it to "reef" and it tiles from launch.
move_stepFlow Points a nudge travels.
resize_stepBoth Points a grow or shrink adds — split either side of the centre in Flow, taken off the neighbour across the split in Reef.
edge_snapFlow How close to an edge a nudge has to land before it sticks to it.
screen_paddingFlow The gap left around a snapped or filled window.
gaps_inReef Space around each tile, so two neighbours sit 2 × this apart.
gaps_outReef Space between the outermost tiles and the edge of the screen.
min_tile_sizeReef The smallest tile any resize may leave behind — keyboard, drag or border pull alike.
workspacesReef How many workspaces each display gets, 1–9. Each one is a dwindle tree of its own.
workspace_hideReef How a hidden workspace is put away. "minimize" sends its windows to the Dock instead, for apps that refuse to be moved off screen.
mouse_modifierReef Held to turn a drag into a layout gesture. "leader" gives the one-modifier Hyprland feel; "" turns it off.
mouse_edge_dragReef Whether dragging a tiled window's own border reshapes the tiling around it.
animation_stiffnessBoth Spring constant. Higher gets there sooner.
animation_dampingBoth Friction. Lower overshoots and wobbles back.

Key names a binding will accept

Arrowsleft · right · up · down
Specialreturn · space · tab · escape · delete
Lettersa – z
Numbers0 – 9
Punctuation- = [ ] ; ' , . / \ `
Modifiersctrl · shift · cmd · opt / alt · leader

Join them with +. The word leader stands in for whatever leader is set to, so rebinding one line changes every shortcut at once.

Reference

All 45 of them, both ways.

The whole surface area of the app, grouped the way the menu, the cheat sheet and the config file all group it — one catalogue in the source feeds the three of them, and this table. One column of keys, because there is only one column of keys: the rows with nothing in the Flow column are Reef's, and in Flow they are not bound at all. The two numbered runs — nine keys to show a workspace, nine to send a window to one — collapse to a row each, exactly as they do in the menu.

Focus
⌃ ←focus_leftFocus window leftFocus tile left
⌃ →focus_rightFocus window rightFocus tile right
⌃ ↑focus_upFocus window upFocus tile up
⌃ ↓focus_downFocus window downFocus tile down
⌃⌥ ⇥cycle_nextunbound — passes through to the appCycle focus forward
⌃⌥⇧ ⇥cycle_prevunbound — passes through to the appCycle focus back
Move
⌃⌥ ←move_leftNudge window leftSwap tile left
⌃⌥ →move_rightNudge window rightSwap tile right
⌃⌥ ↑move_upNudge window upSwap tile up
⌃⌥ ↓move_downNudge window downSwap tile down
⌃ ↩centerCenter windowPromote to master
Resize
⌃⇧ →grow_widthGrow widthWiden split
⌃⇧ ←shrink_widthShrink widthNarrow split
⌃⇧ ↑grow_heightGrow heightHeighten split
⌃⇧ ↓shrink_heightShrink heightShorten split
Snap & Displays
⌃⌘ ←snap_leftSnap left ½ ⅓ ⅔Send to display left
⌃⌘ →snap_rightSnap right ½ ⅓ ⅔Send to display right
⌃⌘ ↑snap_upSnap top ½ ⅓ ⅔Send to display up
⌃⌘ ↓snap_downSnap bottom ½ ⅓ ⅔Send to display down
Layout
⌃⇧ ↩fillFill screenToggle fullscreen
⌃⇧ Stoggle_splitunbound — passes through to the appToggle split direction
⌃⇧ Ftoggle_floatunbound — passes through to the appToggle floating
Workspaces
⌃ 1…9workspace_1…unbound — passes through to the appShow workspace 1–9
⌃⇧ 1…9move_to_workspace_1…unbound — passes through to the appSend window to workspace 1–9
⌃⌥ ]workspace_nextunbound — passes through to the appNext workspace
⌃⌥ [workspace_prevunbound — passes through to the appPrevious workspace
⌃ `workspace_recentunbound — passes through to the appLast workspace
Shaka
⌃ /toggle_modeSwitch to Reef 👊Switch to Flow 🤙
⌃ Kshow_shortcutsShow the shortcut cheat sheetShow the shortcut cheat sheet

How it works

11 system calls, no daemon.

One event tap at the top of the event stream, the Accessibility API for reading and writing window frames, and the window list for finding neighbours. There is no helper tool, no login item, no launch agent and nothing listening on a port.

The tap sees every keystroke — that is what a tap is — and swallows only the combos in your config, passing everything else straight through. Mouse events come through the same tap, because Reef's drag gestures have to be able to take a click before the app underneath reacts; outside a gesture they are looked at and handed on. If macOS ever disables the tap for taking too long, Shaka notices and switches it back on rather than quietly dying.

Reef adds the one thing Flow never needed: an Accessibility observer on every running app, so a window opening or closing is a notification rather than a poll. The signal is deliberately coarse — individual notifications are unreliable across apps, so any event triggers a full re-scan and diff, coalesced over 120ms, with a five-second timer behind it for apps that post nothing at all. Every AX request is capped at a quarter of a second, so one unresponsive app cannot stall the tap.

On first launch it asks for Accessibility, then polls once a second until you grant it, so the shortcuts start working the moment you tick the box — no relaunch.

Hears the keysCGEvent.tapCreate(tap: .cghidEventTap)
Hears the mouseeventsOfInterest: .leftMouseDragged | .rightMouseDragged | …
Finds your windowAXUIElementCopyAttributeValue(kAXFocusedWindowAttribute)
Moves and sizes itAXUIElementSetAttributeValue(kAXPosition, kAXSize)
Finds the neighboursCGWindowListCopyWindowInfo(.optionOnScreenOnly)
Raises oneAXUIElementPerformAction(kAXRaiseAction)
Parks a workspacekAXPositionAttribute below the display, or kAXMinimizedAttribute
Watches for new onesAXObserverCreate(pid) + kAXWindowCreatedNotification
Refuses to hangAXUIElementSetMessagingTimeout(systemWide, 0.25)
Knows the displaysNSScreen.screens[…].visibleFrame
Asks permissionAXIsProcessTrustedWithOptions(kAXTrustedCheckOptionPrompt)

Fine print

What it will not do.

Better to read this now than to find out after the install.

It is not signed or notarized

The installer clones the repo and runs swift build -c release on your machine, which is why there is no Gatekeeper dance and why you need the Xcode Command Line Tools.

Re-granting Accessibility is a step

Shaka is ad-hoc signed, so a rebuild changes its signature and macOS quietly revokes the grant while still showing it ticked. Remove it from the list and add it back, or run tccutil reset Accessibility com.wes.shaka. The menu bar shows 🤙⚠ when this has happened.

⌃ + arrows is already taken

macOS gives those to Mission Control, and ⌃ + K is a terminal's kill-to-end-of-line. Turn the first off in Keyboard ▸ Keyboard Shortcuts ▸ Mission Control, rebind the second, or set leader = "opt" and never think about it again.

Reef is experimental

The dwindle layout is the only one implemented — Hyprland's master layout is not there. It is honest about that rather than half-doing it.

The outer edge moves a column

Every edge is one split in the tree, so dragging the outer edge of a tile resizes the whole column it belongs to. Hyprland's dwindle behaves the same way: that split is genuinely shared by everything in the column.

Workspaces are Shaka's, not Spaces

They live entirely inside Reef and leave Mission Control alone, so a parked window still shows up in Mission Control and ⌘-Tab. Reaching it from there switches you to its workspace. Sending a window to a real macOS Space is still a Mission Control gesture.

Some windows say no

A window with a minimum size will refuse to shrink past it, and anything that does not talk to the Accessibility API — the odd game, the odd Electron shell — simply will not budge. In Reef, ⌃⇧ F floats those out of the way.

Flow remembers nothing

No saved layouts, no per-app rules, no restore on login. You press keys, windows move. Reef is the only structure on offer, and it lasts exactly as long as you leave it on.

Reef watches the mouse

The tap takes mouse events as well as keys so a drag can be a layout gesture, and border drags are followed by polling the window's frame at 25 Hz while you hold it. Set mouse_edge_drag = false if you would rather it did not.

The TOML parser is small

About sixty lines: key = value, [sections], # comments, strings, numbers and booleans. Arrays, tables and multi-line strings are not part of the deal.

There is no window

Shaka is LSUIElement, so there is no dock icon and nothing to ⌘-Tab to. The menu bar item is the entire interface — the glyph is the mode, and the number beside it is the workspace.

Go move a window. Then tile all of them.

The script clones the repo, builds it with Swift Package Manager and puts Shaka.app in /Applications. No tap, no DMG, nothing to drag.

$curl -fsSL https://raw.githubusercontent.com/wes/shaka/main/install.sh | bash

macOS 13+ · Xcode Command Line Tools

Installs

15

since 23 August 2026

There is no binary to download, so this is not a download count — it is how many times the repository has been cloned, which is what the install line does before it builds. The bars are the last fourteen days. GitHub keeps only that fortnight, so the total above is stitched together from every window before it, and the live one could not be read just now.

01

Run the line

Two or three minutes, most of it Swift compiling. It prints what it is doing, and finishes with the two-mode shortcut table.

02

Launch it

From Spotlight or /Applications. The 🤙 appears in the menu bar; nothing appears in the dock.

03

Tick the box

System Settings ▸ Privacy & Security ▸ Accessibility ▸ Shaka. The shortcuts start working straight away — no relaunch.

Prefer to look first? git clone the repo and run make install. Updating? Re-grant Accessibility afterwards — the ad-hoc signature changes with every build. Changed your mind? rm -rf /Applications/Shaka.app ~/.config/shaka and remove it from Accessibility.