GridTile is a tactile-inspired window tiling system for macOS, implemented as a Hammerspoon Spoon.
It lets you tile the currently focused window by selecting regions on a weighted, character-addressable grid — using only the keyboard. If you’ve used tactile on Linux, this brings the same spatial, muscle-memory–friendly workflow to macOS.
A short demo video is included in this repository that shows the full workflow end-to-end:
- Triggering the grid overlay
- Selecting grid cells using characters
- Tiling the focused window
👉 Watch the demo before reading further — it explains the idea faster than text.
GridTile works by overlaying a grid on the screen:
- The grid has rows and columns with configurable weights
- Each cell is assigned a keyboard character
- You press one or two characters to define a region
- The currently focused window is tiled to that region
There is:
- No mouse interaction
- No window selection UI
- No automatic tiling
Everything is explicit and user-driven.
-
Tap F3 twice
-
Works whether F3 is:
- Used directly as a function key (Stage Manager)
- Accessed via the Fn modifier
- Used as a Fn key (F3)
This is handled using Karabiner-Elements to avoid conflicts with:
- Mission Control
- Stage Manager
- Fn / Function key mode differences
-
Each grid cell is labeled with a normal keyboard character
- Examples:
a,s,d,1,2,Q,W,;,,
- Examples:
-
Press:
- One character → tile to that single cell
- Two characters → tile to the bounding rectangle formed by those cells
-
The currently focused window is always used
-
If an application has minimum size constraints:
- GridTile will expand the window beyond the selected region as needed
- This is expected behavior and not considered an error
- Press Escape at any time to exit without tiling
- Invalid or unmapped characters are silently ignored
- Pressing only one character is valid
- Pressing Escape always exits cleanly
GridTile is configured directly in the Spoon’s Lua file.
This is intentional — the grid definition is code, not UI.
-- ====================================================================================================
-- USER CONFIGURATION START
-- ====================================================================================================
local columnWeights = {1, 2, 3, 3, 3, 3, 2, 1}
local rowWeights = {1, 2, 3, 2, 1}
-- lua-format off
local letters = {
"1", "2", "3", "4", "7", "8", "9", "0",
"q", "w", "e", "r", "u", "i", "o", "p",
"Q", "W", "E", "R", "U", "I", "I", "P",
"a", "s", "d", "f", "j", "k", "l", ";",
"z", "x", "c", "v", "n", "m", ",", "."
}
-- lua-format on
-- Padding between grid cells
local padding = 10
-- ====================================================================================================
-- USER CONFIGURATION END
-- ====================================================================================================- Number of columns → length of
columnWeights - Number of rows → length of
rowWeights - Padding between the grids →
padding - Relative sizes → values inside the weight arrays
- Cell labels → contents of
letters
- Grid size is inferred as: rows × columns
- The number of entries in
lettersmust matchrows × columns - Characters are assigned in row-major order (top-left → bottom-right)
The formatting of the configuration tables is intentional.
If you use StyLua, the config section is protected using ignore directives to prevent auto-formatting from rearranging the grid visually.
Install Hammerspoon and grant:
- Accessibility permissions
- Screen Recording permissions (required for overlay rendering)
Clone or copy the GridTile.spoon directory into:
~/.hammerspoon/Spoons/Your directory structure should look like:
~/.hammerspoon/
├── init.lua
└── Spoons/
└── GridTile.spoon/
└── init.lua
Add the following line to your ~/.hammerspoon/init.lua:
-- Load the GridTile spoon
hs.loadSpoon("GridTile")
hs.hotkey.bind({}, "F18", function()
spoon.GridTile:start()
end)GridTile does not bind traditional hotkeys directly. It is triggered via Karabiner-managed key events.
Reload Hammerspoon after making changes.
GridTile is configured directly inside the Spoon’s Lua file.
This is intentional — the grid definition is code, not UI - refer to the User Configuration section above
GridTile relies on Karabiner-Elements to reliably map Mission Control to the F18 key.
- Open Karabiner-Elements
- Go to Function Keys
- Change the Fn + f3 to f18
This step is required.
-
macOS
-
Hammerspoon
- Accessibility permissions
- Screen Recording permission (for overlay display)
-
Karabiner-Elements
Multi-monitor behavior has does not work across mutli-screen setups.
Currently, GridTile only works on the primary screen.
- Designed primarily for QWERTY-style layouts
- Custom layouts and remapped keyboards may require adjusting the
letterstable
GridTile is intentionally not:
- A full tiling window manager
- An automatic layout engine
- A background daemon
It is:
- Explicit
- Keyboard-driven
- Spatial
- Fast
You decide exactly where a window goes, every time.
GridTile is inspired by the Linux tool tactile, adapting its spatial, character-based region selection model to macOS using Hammerspoon.
This project is actively used but still evolving.
- APIs may change
- Configuration structure may improve
- Feedback and issues are welcome
