Layout

September 7, 2026 ยท View on GitHub

Choose one of Umbriel's three layout modes for each workspace: Scrolling, Dwindle, or Master. Configure a default mode globally, then override it for individual workspaces when needed. The initial scrolling width can also have an output-specific default.

Choose a layout

[layout]
mode = "scrolling" # "scrolling", "dwindle", or "master"
ModeArrangementBest suited to
scrollingColumns form a scrollable strip. Each column can contain stacked windows.Workflows that keep many windows available without shrinking every column.
dwindleEach new window splits an existing tile.Recursive tiling with independently sized regions.
masterWindows occupy a master area and a stack area.Keeping one or more primary windows separate from the rest.

The active mode can also be changed with workspace-set-layout:<mode>; see Actions. A workspace rule can set a persistent per-workspace mode; see Workspace Rules.

Column-scoped actions operate on the active layout's column projection. Scrolling owns real multi-window columns. In dwindle, every tiled leaf is a single-window column, so column actions act on that window. In master, the master and stack areas are the two columns. When an action has no meaning in the active layout, its keybind does nothing and the IPC msg command returns an error naming the required layout.

Shared settings

[layout]
gap = 8
width_presets = [0.333, 0.5, 0.667]
KeyTypeDefaultDescription
gapint8Gap between windows in pixels (0-500).
width_presetsfloat array[0.333, 0.5, 0.667]Fractions used by window-cycle-width and window-cycle-height in every layout. Both actions use this one list.

Struts

[layout.struts]
left = 0
right = 0
top = 0
bottom = 0

Struts reserve signed logical pixels around the normal tiled layout. Umbriel applies them after layer-shell exclusive zones, so positive values reserve more space inward from panels, while negative values expand the tiled area and can place windows beneath panels or beyond an output edge. Each edge accepts a value from -65535 to 65535.

All three tiled layouts use the resulting area. On the scrolling axis, struts leave room beyond the viewport where neighboring lanes can remain visible. Floating windows and popups ignore struts. Maximize and fullscreen treat struts differently from the tiled layout; see Sizing behavior.

Mod+Right-drag selects horizontal and vertical resize edges from the outer thirds of tiled and floating windows. Dragging from a corner resizes both axes. Mod+Right-click in the center starts no resize and preserves the window's maximize state. For a tiled window, a center click also scrolls the focused window into view. When a tiled resize ends, a focused scrolling column animates back into view. In Dwindle, only edges backed by an internal split propose a resize; screen-facing edges propose nothing.

Scrolling layout

Scrolling keeps columns at their configured widths and moves the strip through a viewport. A column can contain multiple windows. The strip axis is always perpendicular to the owning output's workspace axis: an output whose workspaces are arranged vertically scrolls horizontally, and one whose workspaces are arranged horizontally scrolls vertically.

Settings

[layout.scrolling]
default_width_fraction = 0.5         # remove to let clients choose, 0.1-1.0
center_underfull_strip = true
center_focused = false
expand_single_column = true
KeyTypeDefaultDescription
default_width_fractionfloatunsetInitial strip-axis extent for new columns (0.1-1.0). The packaged config sets 0.5; a matching output or workspace rule can override it. When it is unset at every level, the client chooses its initial extent.
center_underfull_stripbooltrueCenter the complete strip when it is shorter than the viewport. Disable to align it at the start edge.
center_focusedboolfalseAlways center the focused column.
expand_single_columnboolfalseFill the viewport for a workspace's lone tiled column. Client size hints and viewport bounds still apply. The packaged config enables this.

Horizontal and vertical scrolling

Workspace axisLayoutWidth and height actions
vertical (default)Columns run left to right. Windows within a column stack from top to bottom.Width actions change a column's strip extent. Height actions change a window's extent within its column.
horizontalHorizontal lanes run top to bottom. Windows within a lane sit side by side.Width actions change a lane's strip extent, which is its height on screen. Height actions change a window's extent within its lane, which is its visual width.

On an output with horizontal workspaces, directional actions follow the screen; see Vertical strips. The consume and expel actions use the same visual directions: left and right merge or split within the lane, while the resulting lane is above or below the focused lane.

A three-finger swipe along the output's workspace axis switches workspaces. A three-finger swipe across it scrolls the strip, and is inert when the workspace has no scrolling layout. Release velocity settles the swipe against a viewport edge.

Scrolling behavior

The packaged config sets default_width_fraction = 0.5, so new columns start at half the viewport. If the option is omitted, Umbriel leaves the strip-axis extent unconstrained during the initial configure and retains the logical size chosen by the client. A numeric default_width window rule takes precedence. For a new horizontal column, the pixel width from a matching default_size window rule takes precedence over both fractional settings.

Fractional widths round cumulative column boundaries to logical pixels. When a viewport and its gaps cannot divide evenly, the remaining pixel is assigned to one column so the complete span still fits exactly. This applies equally to odd and even gap values and keeps focus changes from shifting an otherwise fully visible strip.

Set a different initial width for every scrolling workspace on one output under that output's section:

[output.DP-1.layout.scrolling]
default_width_fraction = 0.4

[output."Microstep MSI G2712F CD6T084401192".layout.scrolling]
default_width_fraction = 0.6

Connector and monitor names follow the normal output identity rules. A monitor-named output section wins when both it and a connector section match the same display.

The initial width is resolved from the global setting, then the matching output setting, then a matching workspace rule without an output, and finally a matching workspace rule with an output. Each later value takes precedence. Only default_width_fraction has this output-level layout override; the other layout settings remain global or per workspace.

Changing any of these defaults on reload affects columns created afterward. It does not resize existing columns, and a column moved to another output retains its stored fraction. Re-tiling a floating window or expelling a window into a new column creates a column using the current default.

expand_single_column affects only how a lone tiled column is displayed. It does not rewrite the stored fraction, so the configured or client-selected width applies again when a second column appears. Explicit default_maximize and default_maximize_to_edges window rules take precedence.

When focus moves to a hidden or partially hidden column, Umbriel reveals it by the shortest distance needed to show it completely. A column entering from the right aligns with the right viewport edge, and one entering from the left aligns with the left edge. Focus does not reserve a visible sliver for the next column. Resizing a column recenters an underfull strip immediately.

Dragged windows become translucent so the insertion preview remains visible. Existing window transparency still applies during the drag. When a column is dragged, the preview uses the free space beside the real column edges. If the strip extends beyond the output, its far left and right edges remain available as prepend and append targets even when the end columns are off-screen.

Dropping a window into empty space above or below a vertically resized stack consumes that space. Existing windows retain their pixel heights, and the dropped window fills the remainder apart from the configured gap.

Vertical strips

On an output with horizontal workspaces the strip is vertical, and directional actions follow their visual directions. window-focus-left and window-focus-right move within a lane, while window-focus-up and window-focus-down walk lanes. Likewise, column-move-left and column-move-right reorder within a lane, while window-move-up and window-move-down move the lane along the strip. layout-scroll-left and layout-scroll-up both scroll toward strip start; their right and down forms scroll toward strip end.

The default Mod+wheel bindings invoke window-focus-left and window-focus-right, so they move within a lane. Configurations using vertical strips should bind wheel chords to window-focus-up and window-focus-down, or to layout-scroll-up and layout-scroll-down.

Dwindle layout

Dwindle recursively splits tiles into independently sized regions.

Settings

[layout.dwindle]
preserve_split = false # keep each split direction fixed after creation
KeyTypeDefaultDescription
preserve_splitboolfalseKeep each split direction fixed after it is created.

Behavior

A new window splits the focused tile along its longer edge and becomes the right or bottom half. With preserve_split = false, split directions follow each tile's shape as geometry changes, so closing a window can reflow surviving splits. Dropping on a specific edge keeps that direction. Set preserve_split = true to freeze every split direction when it is created.

When a window or column moves onto a Dwindle workspace, a multi-window column is flattened into ordered single-window columns. This is also how drag-and-drop handles a column that enters Dwindle.

Master layout

Master divides the workspace into a master area and a stack area. The master area is on the side selected by position; the stack occupies the other side. Each area arranges its windows from top to bottom. When only one area has windows, that area fills the complete content box.

Settings

[layout.master]
position = "left"                   # "left" or "right"
default_width_fraction = 0.55       # 0.1-0.9
new_on_top = true                    # place new windows at the top of the stack
KeyTypeDefaultDescription
positionstring"left"Side occupied by the master area: "left" or "right".
default_width_fractionfloat0.55Initial fraction assigned to the master area when both areas exist (0.1-0.9).
new_on_topbooltruePlace new windows at the top of the stack. Disable to place them at the bottom.

Behavior

The first window becomes master. A new window also becomes master when the master area is empty. Otherwise it joins the top of the stack when new_on_top = true, or the bottom when it is false. Removing the final master window promotes the top stack window. Moving every window out of master does not promote one, so the remaining stack stays full-width until another window opens or is moved into master.

Consume actions preserve their visual meanings. With position = "left", left moves a stack window into master and right moves a master window into the stack. With position = "right", those roles reverse because master is visually right. The consume-or-expel variants make the same directional move.

Master workflows use a deterministic layout-order ring: master windows from top to bottom, then stack windows from top to bottom. window-focus-next and window-focus-previous cycle through that ring and wrap. The swap actions exchange the focused window with its neighboring slot while keeping focus on that window. layout-master-count-increase promotes the stack top into master, and layout-master-count-decrease demotes the master bottom into the stack. At least one window remains in master.

Width actions operate on the master fraction; the stack fraction is its complement. window-modify-width:<delta> changes the focused area's fraction, and the cycle actions walk width_presets. Width actions are inert while either area is empty because the occupied area already fills the viewport. Height actions change a window's row fraction within its area; see Sizing behavior. Tiled resizing is available on the boundary between master and stack and between rows in either area.

Dragging over a master workspace previews the destination row within the nearest area. Hint bands appear at the top, bottom, and between existing rows. Dropping inserts the window at that row.

Sizing behavior

The width and height actions are shared by every layout, but each layout gives them a different meaning.

window-modify-width:<delta> changes the focused area's width by a signed fraction, and window-cycle-width / window-cycle-width-back walk width_presets in either direction.

window-set-height:<frac> sets the focused window's fraction of its column's stacking extent, window-modify-height:<delta> changes that fraction by a signed amount, and window-cycle-height / window-cycle-height-back cycle it through the same presets in either direction. In scrolling and master layouts this sizes a row within its column or area. In dwindle it adjusts the vertical splits containing the window. On a vertical strip the stacking axis is horizontal, so these actions change a window's width within its lane.

In the scrolling layout, a window alone in its column is resized from its bottom edge, exactly as dragging that edge does: the top edge stays where it is and the freed space collects below the window, so the next window stacked into that column fills it. A fraction of 1.0 reclaims the space. A window that a previous drag pushed against the column's bottom keeps that anchor and frees space above itself instead. In master and dwindle, a window with no neighbor on the stacking axis has nothing to trade space with, so the height actions leave it unchanged.

Floating windows

All of the width and height actions resize a focused floating window directly, as fractions of the output's usable area clamped to the client's min/max size hints. Cycling walks width_presets on either axis, stepping to the next preset that changes the window's pixel size on that axis: a float's size is pixels, so a preset that rounds to the size the window already has is skipped rather than applied as a step that does nothing. Resizing a maximized float leaves maximization behind and keeps the new size, so a later toggle maximizes rather than reverting to the pre-maximize box. Both axes use the animation.windows_move transition, and a float that hangs off an edge travels with the resize so the same part of it stays on screen at the new size. Fullscreen owns the size outright, so the actions do nothing while a float is fullscreen.

Parented XDG dialogs are stacked with their ancestor chain. Raising any member raises the family while keeping each dialog above its parent, including when an ancestor is floating, pinned, or fullscreen.

Maximize and fullscreen

window-toggle-fullscreen ignores layout struts and layer-shell exclusive zones and fills the entire output. It normally targets the focused window. If another fullscreen window completely covers that focus on the active output, the action exits the covering fullscreen window first and leaves focus in place. Leaving fullscreen sends the restored tiled or floating size with the windowed configure, including for XWayland windows, so the action has no timer-delayed fallback. window-toggle-maximize toggles the focused column's full-width state, and a tiled column stays inside configured struts and gaps. A floating window has no column, so it fills the output's usable area and restores its exact previous box, including its last dropped position. Both directions use the animation.windows_move transition. window-toggle-maximize-to-edges drops layout struts, gaps, and borders, while layer-shell exclusive zones remain visible. A column's full-width restore state survives that toggle and a fullscreen round trip. In the scrolling layout the strip reserves the strut band such a column reaches past, so neighboring columns keep their gap instead of sitting underneath the window.