Skip to content

Compose

Compose divides the workarea into regions, each with its own layout. Create IDE-style layouts, presentation modes, or any custom arrangement.

Horizontal split

Divide left to right:

local Compose = require("layouts.combinators.compose")
local Tall = require("layouts.tall")
local Column = require("layouts.column")
local devLayout = Compose.horizontal({
{ ratio = 0.65, layout = Tall, count = 1 },
{ ratio = 0.35, layout = Column },
}, {
name = "developer",
displayName = "Developer",
})

This creates:

  • Left 65%: First window in tall layout
  • Right 35%: Remaining windows stacked in column

Vertical split

Divide top to bottom:

local Compose = require("layouts.combinators.compose")
local Fullscreen = require("layouts.fullscreen")
local Column = require("layouts.column")
local presentLayout = Compose.vertical({
{ ratio = 0.85, layout = Fullscreen, count = 1 },
{ ratio = 0.15, layout = Column },
}, {
name = "presentation",
displayName = "Presentation",
})

This creates:

  • Top 85%: Main presentation window
  • Bottom 15%: Speaker notes in column

Window assignment

Each split specifies how many windows it receives:

  • count = N: Assign exactly N windows to this region
  • count omitted: Assign all remaining windows

Windows are assigned in order. The first split gets its count, then the next, and so on. The last split (or any without count) receives whatever remains.

Compose.horizontal({
{ ratio = 0.5, layout = Tall, count = 2 }, -- First 2 windows
{ ratio = 0.5, layout = Grid }, -- All remaining windows
})

Nested composition

Compose layouts can contain other Compose layouts:

local Compose = require("layouts.combinators.compose")
local Tall = require("layouts.tall")
local Grid = require("layouts.grid")
local Fullscreen = require("layouts.fullscreen")
local ideLayout = 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",
})

This creates:

  • Left 60%: 2 master windows (tall layout)
  • Right top 20%: 2 windows in grid
  • Right bottom 20%: Remaining windows fullscreen

Ratio normalization

Ratios are normalized to sum to 1.0. These are equivalent:

-- Explicit fractions
{ ratio = 0.6 }, { ratio = 0.4 }
-- Any proportional values
{ ratio = 3 }, { ratio = 2 } -- Same as 0.6, 0.4
{ ratio = 60 }, { ratio = 40 } -- Same as 0.6, 0.4

Error handling

Compose returns nil and an error string on invalid input:

-- Compose requires valid splits
local layout, err = Compose.horizontal({
{ ratio = 0, layout = Tall }, -- ratio must be positive
})
-- err = "ratio at index 1 must be a positive number"

State management

Compose maintains state for child layouts. Stateful layouts (like BSP with custom splits) preserve their state when wrapped in Compose. State persists across layout cycles and Hammerspoon restarts.