Workspaces

September 9, 2026 ยท View on GitHub

Choose a workspace model, inspect its state, and customize layout per workspace.

Choose a workspace model

Each output can use dynamic or static workspaces, up to 64 including empty ones. At the limit, a workspace transfer reuses an empty destination on a dynamic output and is rejected when it would require another workspace.

Dynamic workspaces

Omit workspaces or set it to "dynamic". By default, the output starts with one empty workspace named "1". With empty_above = true, it starts with distinct leading and trailing empty workspaces named "1" and "2".

When the last workspace gains a window, Umbriel adds another empty workspace. With empty_above = true, it also adds a new leading empty workspace when the first workspace gains a window.

After you leave any other empty workspace, Umbriel removes it unless it is still active. The remaining workspaces are renumbered. If you switch to a workspace number beyond the current count, Umbriel uses the last workspace.

min_workspaces sets a floor on the count for one output, so a bar or overview can always show that many entries:

[output.DP-1]
min_workspaces = 3

The floor is per output and is not a ceiling. DP-1 keeps three workspaces while all of them are empty, and a window on the third still adds a fourth. Pruning stops once three remain. The key belongs to a dynamic output: setting it alongside a static workspaces inventory is a configuration error.

Static workspaces

Set workspaces to a number or an ordered list of names. Umbriel creates exactly those workspaces and keeps them when they are empty.

Actions that take a workspace argument resolve names and numbered positions the same way here as everywhere else; see Workspace selectors.

[output.DP-1]
workspaces = 5

[output.DP-2]
workspaces = ["WEB", "CHAT", "VIDEO"]

Change workspaces on reload

Workspace changes apply when you save a valid configuration. For static workspaces, Umbriel first matches existing workspaces by name and then by position. Windows from a removed workspace move to the nearest remaining one.

Switching to dynamic workspaces keeps populated and active workspaces, renumbers them, and adds an empty workspace at the end.

Other output and layout settings are refreshed during a reload as well.

Workspace axis

Each output arranges its workspaces along one axis. workspace_axis selects it per output and accepts "vertical" (the default) or "horizontal":

[output.DP-1]
workspace_axis = "horizontal"

Vertical workspaces stack top to bottom: switching slides the previous workspace down and the next one up, and the overview shows one vertical filmstrip. With "horizontal", workspaces sit side by side, switching slides them left and right, and the overview filmstrip runs horizontally.

The axis also selects the scrolling strip axis, which is always perpendicular to it. Vertical workspaces scroll horizontally and horizontal workspaces scroll vertically; see Scrolling layout. Dwindle and master workspaces follow their output's axis for switching and the overview, like every other layout.

A three-finger swipe along the axis switches workspaces, and a swipe across it scrolls the strip. In the overview, the ordinary vertical wheel navigates either arrangement, while a horizontal wheel navigates only horizontal workspaces.

An axis change on reload applies to every workspace on the output. Any live swipe, strip drag or tiled resize is settled first, and an open overview closes.

Workspace selectors

Actions that take a workspace argument, such as workspace-switch, window-move-to-workspace, and column-move-to-workspace, resolve it with these rules:

  • Exact names resolve globally first, including numeric names.
  • A unique name selects its workspace on any output.
  • Duplicate names resolve on the preferred output.
  • When no exact numeric name exists, the number selects that 1-based position on the preferred output.
  • On a dynamic output, a number beyond the current workspace list selects the last workspace.
  • Add /output to target another output explicitly.

For example, workspace-switch:3 selects a workspace named 3 when one exists and otherwise the third workspace on the preferred output, while workspace-switch:CHAT/HDMI-A-1 selects CHAT on HDMI-A-1.

When workspace-switch targets another monitor, the cursor warps to that monitor's center so subsequent actions continue there. Window and column moves do the same by default. With input.cursor.follows_focus enabled, they warp to the moved focused window's visible center instead.

Inspect workspace state

Run umbriel workspaces to list every workspace with its output and effective layout mode. An asterisk marks the active workspace on each output, while (focused) identifies the active workspace on the output Umbriel currently targets for actions.

* DP-1: 1 [scrolling] (focused)
  DP-1: 2 [dwindle]
* DP-2: WEB [master]

Use umbriel workspaces --json for structured output. Each entry contains the workspace id, name, one-based index, output, active, focused, and layout. active is per output, so more than one workspace can be active. focused is true for at most one workspace. The layout value is the current effective mode, including an override made with workspace-set-layout.

For example, this prints the layout on the workspace currently targeted by workspace actions:

umbriel workspaces --json | jq -r '.[] | select(.focused).layout'

Global workspace settings

[workspaces]
back_and_forth = true
empty_above = false
KeyTypeDefaultDescription
back_and_forthboolfalseRe-selecting the active workspace jumps back to the previously active workspace on that output.
empty_aboveboolfalseAdd an empty workspace at the start, in addition to the workspace at the end.

Output workspaces are dynamic by default. workspaces and min_workspaces are per-output keys documented in Outputs. The workspace models and rules are documented below.

Workspace rules

[[workspace]] entries customize static workspaces or numbered positions on a dynamic output. They change layout settings but do not create workspaces.

Each rule selects a workspace by exactly one of name (string) or index (1-based integer from 1 to 64). An optional output restricts the rule to a case-insensitive connector or monitor name from umbriel outputs.

How settings are combined

Workspace layout settings are applied in this order:

  1. The base [layout] settings.
  2. The matching output's layout.scrolling.default_width_fraction, when configured.
  3. A matching [[workspace]] rule without an output.
  4. A matching [[workspace]] rule for the selected output.

Later steps take precedence. On dynamic outputs, rules match workspace names and numbered positions as those workspaces are created or removed. The output step applies only to default_width_fraction; other layout fields pass directly from the global settings to workspace rules.

Strut edges are resolved independently. A rule that sets only layout.struts.top inherits the other three edges from earlier steps.

Available fields

KeyTypeDescription
namestringSelect by workspace name (mutually exclusive with index).
indexintSelect by 1-based position from 1 to 64 (mutually exclusive with name).
outputstringRestrict to a connector or monitor name.
layout.modestring"scrolling", "dwindle", or "master".
layout.gapintGap in pixels (0-500).
layout.struts.{left,right,top,bottom}intSigned logical pixels reserved at each edge of the normal tiled layout (-65535 to 65535). Positive values shrink the area and negative values expand it.
layout.width_presetsfloat arrayFractions used by the width-cycle and height-cycle actions in every layout.
layout.scrolling.default_width_fractionfloatOptional initial scrolling lane extent (0.1-1.0). It overrides the global and matching output values. When omitted at every level, the client chooses its initial logical extent. Reloading a default does not resize existing columns.
layout.scrolling.center_underfull_stripboolCenter the complete strip whenever it is narrower than the viewport. Disable to left-align underfull strips.
layout.scrolling.center_focusedstringWhen a focus change centers the newly focused column. "never" only scrolls far enough to reveal it, "always" centers it, and "on_overflow" centers it when it cannot share the viewport with the neighboring column on the side focus came from.
layout.master.positionstringSide occupied by the master area: "left" or "right".
layout.master.default_width_fractionfloatMaster area fraction when both areas exist (0.1-0.9).
layout.master.new_on_topboolPlace newly opened windows at the top of the stack. Disable to place them at the bottom.
layout.dwindle.preserve_splitboolKeep each Dwindle split direction fixed after it is created when true.

Examples

# Dwindle layout for the VIDEO workspace on DP-2
[[workspace]]
output = "DP-2"
name = "VIDEO"
layout.mode = "dwindle"

# Scrolling for CHAT, dwindle for STATS, both on HDMI-A-1
[[workspace]]
output = "HDMI-A-1"
name = "CHAT"
layout.mode = "scrolling"
layout.scrolling.center_focused = "always"

[[workspace]]
output = "HDMI-A-1"
name = "STATS"
layout.mode = "dwindle"

# Customize workspace position 4 on DP-1
[output.DP-1.layout.scrolling]
default_width_fraction = 0.5

[[workspace]]
index = 4
output = "DP-1"
layout.gap = 0
layout.struts.top = 24
layout.scrolling.default_width_fraction = 0.667

In the last example, new columns on DP-1 use 0.5 except on workspace position 4, where the more specific workspace rule uses 0.667.