Skip to content

Complete examples

These examples demonstrate how to combine Shoji’s building blocks for practical workflows.

Developer setup

A complete configuration for software development with multiple layout options.

-- ~/.hammerspoon/init.lua
hs.loadSpoon("Shoji")
local shoji = spoon.Shoji
-- Access building blocks
local Modifiers = shoji.Modifiers
local Compose = shoji.Combinators.Compose
local Tall = shoji.Layouts.Tall
local Wide = shoji.Layouts.Wide
local Grid = shoji.Layouts.Grid
local BSP = shoji.Layouts.BSP
local Column = shoji.Layouts.Column
local Fullscreen = shoji.Layouts.Fullscreen
------------------------------------------------------------------------
-- Modified layouts
------------------------------------------------------------------------
-- Magnified tall: focused window gets 1.4x larger
local MagnifiedTall = Modifiers.wrap(
Tall,
Modifiers.Magnifier.create({ ratio = 1.4, reflow = true })
)
-- Mirrored tall: master on right instead of left
local TallRight = Modifiers.wrap(
Tall,
Modifiers.Mirror.horizontal(),
{ name = "tall-right", displayName = "Tall (Right)" }
)
------------------------------------------------------------------------
-- Composed layouts
------------------------------------------------------------------------
-- Developer: main editor (65%), reference stack (35%)
local Developer = Compose.horizontal({
{ ratio = 0.65, layout = Tall, count = 1 },
{ ratio = 0.35, layout = Column },
}, {
name = "developer",
displayName = "Developer",
})
-- IDE: editor (60%), grid + terminal (40%)
local IDE = Compose.horizontal({
{ ratio = 0.6, layout = Tall, count = 2 },
{ ratio = 0.4, layout = Compose.vertical({
{ ratio = 0.5, layout = Grid, count = 2 },
{ ratio = 0.5, layout = Fullscreen },
})},
}, {
name = "ide",
displayName = "IDE Layout",
})
-- Presentation: main content (85%), notes strip (15%)
local Presentation = Compose.vertical({
{ ratio = 0.85, layout = Fullscreen, count = 1 },
{ ratio = 0.15, layout = Column },
}, {
name = "presentation",
displayName = "Presentation",
})
------------------------------------------------------------------------
-- Configuration
------------------------------------------------------------------------
shoji:start({
layouts = {
MagnifiedTall,
TallRight,
Developer,
IDE,
Presentation,
},
enabled_layouts = {
"tall",
"developer",
"ide",
"fullscreen",
},
gap_outer = 8,
gap_inner = 8,
retile_on_focus = true, -- Required for magnifier
})
shoji:bindHotkeys({
-- Layout cycling
cycle_layout = { { "cmd", "ctrl", "alt" }, "space" },
-- Window focus
focus_next = { { "ctrl", "alt" }, "'" },
focus_prev = { { "ctrl", "alt" }, ";" },
-- Window swapping
swap_main = { { "cmd", "ctrl", "alt" }, "return" },
-- Layout adjustments
increase_main_ratio = { { "ctrl", "alt" }, "." },
decrease_main_ratio = { { "ctrl", "alt" }, "," },
-- Float toggle
toggle_float = { { "ctrl", "alt", "shift" }, "t" },
})

Keybindings summary

KeysAction
cmd+ctrl+alt+spaceCycle layouts
ctrl+alt+'Focus next window
ctrl+alt+;Focus previous window
cmd+ctrl+alt+returnSwap with main
ctrl+alt+.Expand main area
ctrl+alt+,Shrink main area
ctrl+alt+shift+tToggle floating

Ultrawide monitor setup

Extra gaps and centered layouts work well on ultrawide displays.

hs.loadSpoon("Shoji")
local shoji = spoon.Shoji
local Modifiers = shoji.Modifiers
local Tall = shoji.Layouts.Tall
local BSP = shoji.Layouts.BSP
-- Centered at 85% with extra gaps
local CenteredTall = Modifiers.chain(Tall, {
Modifiers.Centered.ratio(0.85),
Modifiers.Gaps.add({ outer = 20, inner = 12 }),
}, {
name = "centered-tall",
displayName = "Centered Tall",
})
-- BSP with breathing room
local SpacedBSP = Modifiers.chain(BSP, {
Modifiers.Gaps.add({ outer = 24, inner = 8 }),
}, {
name = "spaced-bsp",
displayName = "Spaced BSP",
preserveActions = true, -- Keep BSP resize actions
})
shoji:start({
layouts = { CenteredTall, SpacedBSP },
enabled_layouts = { "centered-tall", "spaced-bsp" },
gap_outer = 16,
gap_inner = 8,
})

Minimal configuration

A simple setup with just the essentials.

hs.loadSpoon("Shoji")
local shoji = spoon.Shoji
local Modifiers = shoji.Modifiers
local Tall = shoji.Layouts.Tall
-- Tall with master on right
local TallRight = Modifiers.wrap(
Tall,
Modifiers.Mirror.horizontal(),
{ name = "tall-right", displayName = "Tall (Right)" }
)
shoji:start({
layouts = { TallRight },
enabled_layouts = { "tall", "tall-right", "fullscreen" },
gap_outer = 8,
gap_inner = 8,
})
shoji:bindHotkeys({
cycle_layout = { { "cmd", "ctrl", "alt" }, "space" },
swap_main = { { "cmd", "ctrl", "alt" }, "return" },
toggle_float = { { "ctrl", "alt", "shift" }, "t" },
})

Focus-aware magnification

Magnify the focused window while keeping others visible.

hs.loadSpoon("Shoji")
local shoji = spoon.Shoji
local Modifiers = shoji.Modifiers
local Tall = shoji.Layouts.Tall
local BSP = shoji.Layouts.BSP
local MagnifiedTall = Modifiers.wrap(
Tall,
Modifiers.Magnifier.create({
ratio = 1.5,
reflow = true,
minSize = 100,
})
)
local MagnifiedBSP = Modifiers.wrap(
BSP,
Modifiers.Magnifier.create({ ratio = 1.3 }),
{ preserveActions = true }
)
shoji:start({
layouts = { MagnifiedTall, MagnifiedBSP },
enabled_layouts = { "tall+magnify", "bsp+magnify" },
retile_on_focus = true, -- Essential for magnifier
})

Building blocks reference

Modifiers

ModifierPurpose
Magnifier.create()Enlarge focused window
Mirror.horizontal()Flip left/right
Mirror.vertical()Flip top/bottom
Gaps.add()Extra spacing
Centered.ratio()Scale and center
Conditional.when()Apply modifier conditionally
Reorder.wrap()Sort windows before layout

Combinators

CombinatorPurpose
Compose.horizontal()Split screen left-to-right
Compose.vertical()Split screen top-to-bottom

Functions

FunctionPurpose
Modifiers.wrap()Apply one modifier
Modifiers.chain()Apply multiple modifiers