Skip to content

Composition recipes

Ready-to-use examples of layout composition. Each recipe shows working code you can adapt.

Centered tall at custom ratio

Scale the tall layout to 70% of screen size, centered:

local Modifiers = require("layouts.modifiers")
local Centered = require("layouts.modifiers.centered")
local Tall = require("layouts.tall")
local centeredTall = Modifiers.wrap(Tall, Centered.ratio(0.7), {
name = "tall-centered",
displayName = "Tall (Centered)",
})
spoon.Shoji:configure({
layouts = { centeredTall },
enabled_layouts = { "tall", "tall-centered" },
})

Adjust the ratio parameter (0.0–1.0) to control how much screen space the layout uses. Useful on ultrawide monitors where full-width windows feel too stretched.

Mirrored wide (main on bottom)

Flip the wide layout vertically so the main window is at the bottom:

local Modifiers = require("layouts.modifiers")
local Mirror = require("layouts.modifiers.mirror")
local Wide = require("layouts.wide")
local wideBottom = Modifiers.wrap(Wide, Mirror.vertical(), {
name = "wide-bottom",
displayName = "Wide (Bottom)",
})
spoon.Shoji:configure({
layouts = { wideBottom },
enabled_layouts = { "wide", "wide-bottom" },
})

The main area moves to the bottom, stack windows appear along the top. Good for setups where you want your primary window at eye level.

Gaps with per-edge values

Add custom spacing around and between windows:

local Modifiers = require("layouts.modifiers")
local Gaps = require("layouts.modifiers.gaps")
local Tall = require("layouts.tall")
-- Uniform gaps
local uniformGaps = Modifiers.wrap(Tall, Gaps.create({
outer = 16,
inner = 8,
}), {
name = "tall-gaps",
displayName = "Tall (Gaps)",
})
-- Per-edge outer gaps (more space on sides than top/bottom)
local sideGaps = Modifiers.wrap(Tall, Gaps.create({
outer = { left = 40, right = 40, top = 10, bottom = 10 },
inner = 8,
}), {
name = "tall-side-gaps",
displayName = "Tall (Side Gaps)",
})
spoon.Shoji:configure({
layouts = { uniformGaps, sideGaps },
})

Per-edge gaps let you compensate for menu bars, docks, or asymmetric monitor bezels. Unspecified edges default to 0.

Partition for IDE layout

Split the screen with dedicated regions for different purposes:

local Partition = require("layouts.combinators.partition")
local Tall = require("layouts.tall")
local Column = require("layouts.column")
local ideLayout = Partition.horizontal({
{ ratio = 0.65, layout = Tall, count = 1 },
{ ratio = 0.35, layout = Column },
}, {
name = "ide",
displayName = "IDE",
})
spoon.Shoji:configure({
layouts = { ideLayout },
enabled_layouts = { "tall", "ide" },
})

Window 1 gets the left 65% (editor). Remaining windows stack in columns on the right (terminal, browser, docs). Adjust count to assign more windows to the main region.

Presentation layout

Vertical partition with a large main area and a row of supporting windows:

local Partition = require("layouts.combinators.partition")
local Monocle = require("layouts.monocle")
local Column = require("layouts.column")
local presentLayout = Partition.vertical({
{ ratio = 0.85, layout = Monocle, count = 1 },
{ ratio = 0.15, layout = Column },
}, {
name = "presentation",
displayName = "Presentation",
})
spoon.Shoji:configure({
layouts = { presentLayout },
})

Top 85% shows one window full-width. Bottom 15% shows supporting windows in a row.

IfMax for adaptive layouts

Switch layouts automatically based on window count:

local IfMax = require("layouts.combinators.if_max")
local Monocle = require("layouts.monocle")
local Tall = require("layouts.tall")
-- Use monocle for 1-2 windows, tall for 3+
local adaptive = IfMax.create(2, Monocle, Tall, {
name = "adaptive",
displayName = "Adaptive",
})
spoon.Shoji:configure({
layouts = { adaptive },
enabled_layouts = { "adaptive", "tall" },
})

With 1–2 windows, you get focused full-screen viewing. Open a third window and tall layout takes over automatically.

Focused coding layout

Start monocle for deep work, switch to BSP when context-switching:

local IfMax = require("layouts.combinators.if_max")
local Monocle = require("layouts.monocle")
local BSP = require("layouts.bsp")
local focusedCoding = IfMax.create(1, Monocle, BSP, {
name = "focused",
displayName = "Focused",
})
spoon.Shoji:configure({
layouts = { focusedCoding },
})

Single window? Full focus. Multiple windows? BSP distributes them evenly.

Combining modifiers and combinators

Wrap combined layouts with modifiers for full customization:

local Modifiers = require("layouts.modifiers")
local Gaps = require("layouts.modifiers.gaps")
local IfMax = require("layouts.combinators.if_max")
local Monocle = require("layouts.monocle")
local Tall = require("layouts.tall")
-- Create adaptive layout first
local adaptive = IfMax.create(2, Monocle, Tall, {
name = "adaptive-base",
})
-- Wrap with gaps modifier
local gappedAdaptive = Modifiers.wrap(adaptive, Gaps.create({
outer = 12,
inner = 8,
}), {
name = "adaptive-gapped",
displayName = "Adaptive (Gaps)",
})
spoon.Shoji:configure({
layouts = { gappedAdaptive },
})

Modifiers apply after the combinator arranges windows, so gaps affect the final result regardless of which child layout is active.