Hammerspoon is a macOS automation tool that uses Lua scripts to control windows, apps, and system events. For window management, it's more powerful than any dedicated app because you write exactly the rules you need.
This guide builds a complete window management system: edge snapping, grid layouts, multi-monitor support, and custom shortcuts — all in pure Lua.
Install Hammerspoon from hammerspoon.org or via Homebrew:
brew install --cask hammerspoonLaunch Hammerspoon, grant accessibility permissions in System Settings → Privacy & Security, then open the config file:
~/.hammerspoon/init.luaThe hs.window module is your primary tool. Start with edge snapping — the most useful window management feature.
local wf = hs.window.filter
local half = hs.layout.left50
-- Snap to left half
hs.hotkey.bind({"cmd","alt"}, "left", function()
local win = hs.window.focusedWindow()
local f = win:frame()
local screen = win:screen():frame()
f.x = screen.x
f.y = screen.y
f.w = screen.w / 2
f.h = screen.h
win:setFrame(f)
end)
-- Snap to right half
hs.hotkey.bind({"cmd","alt"}, "right", function()
local win = hs.window.focusedWindow()
local f = win:frame()
local screen = win:screen():frame()
f.x = screen.x + screen.w / 2
f.y = screen.y
f.w = screen.w / 2
f.h = screen.h
win:setFrame(f)
end)For multi-monitor setups, moving windows between displays is essential. Add full-screen toggle and monitor switching:
Cmd+Alt+Left/Right — snap to screen halvesCmd+Alt+Up — maximize windowCmd+Alt+Down — minimize/restoreCtrl+Alt+Left/Right — move window to previous/next monitorhs.window.animationDuration = 0 to disable animation lag. Windows snap instantly.Save and restore layouts with hs.layout:
local layout = {
{"Safari", nil, display1, hs.layout.left50, nil, nil},
{"Terminal", nil, display1, hs.layout.right50, nil, nil},
{"Slack", nil, display2, hs.layout.maximized, nil, nil},
}
hs.hotkey.bind({"cmd","alt"}, "0", function()
hs.layout.apply(layout)
end)Go beyond basic snapping with these advanced techniques:
hs.grid for a visual grid overlay — press a hotkey, see a grid, type two characters to place the windowhs.screen.watcher and auto-rearrangeWindow management issues and solutions:
hs.screen.allScreens() to debug layouths.window.animationDuration = 0hs.application.runningApplications() to find exact bundle IDsYour window management system is ready. Next steps: