Layout Presets
An Omarchy shell plugin. A bar icon <img src="bar-icon.png" width="18" alt="the bar icon: a rectangle split into two columns"> opens a panel of preset layouts; picking one arranges every tiled window on the focused workspace to match - columns of the widths you chose, windows stacked inside a column, or rows of windows across the screen.
Each row previews the arrangement it produces and is labelled with the split it will actually land on, so the panel shows what you will get rather than what the preset nominally says. Sizes come out exact to the pixel.
<img src="panel.png" alt="The Layout Presets panel: a header naming the workspace's window count and layout, a COLUMNS section and a STACKED section of preset rows each with a proportional preview, and a footer naming the mouse and keyboard shortcuts." width="330">FAQ
How do I put STACKED above COLUMNS? Lead your preset list with a stacked layout. Whichever kind comes first is the section shown first, everywhere.
omarchy bar set sridhar.layout-presets presets \
"25(50/50)-50-25, 50(50-50)/50(33-33-33), 50-50, 25-75-25, 25-25-25-25"
How do I change which presets are offered? Same setting. The list is also
the cycle order, so trimming it shortens the panel and what SUPER+ALT+L steps
through. See Layouts for the syntax.
Where did the STACKED section go? You are on a workspace tiling with
scrolling, which cannot hold a stacked column, so those presets are not
offered there. The panel header names the layout it is looking at. See
Layouts other than dwindle.
A preset vanished from the panel. Two layouts that come out identical at
your current window count are shown once. At three windows 33-33-33 and
25-25-25-25 are both thirds, so only the first is listed. Open another window
and it comes back.
Why don't the numbers match what I typed? The label is the split you will
actually land on, not the preset's raw weights, and it follows the window count.
25-75-25 reads 20 · 60 · 20 on three windows and 20 · 60 · 10 · 10 on four.
I clicked a preset and nothing happened. The workspace was already in that layout, so there was nothing to move. The bar icon pulses on every apply, so you can tell the click landed.
Can I set the width of a single window? Yes, but not with a preset - a lone tiled window has no sibling to resize against. The panel swaps to a width slider when a workspace has one window. See Single-window workspaces.
My single-window width reset. It rides on a global Hyprland option that
reverts to whatever looknfeel.lua says on any config reload.
How do I bind cycling to a key? Two lines in ~/.config/hypr/bindings.lua;
the panel footer then reads your binding back out of the config. See
Command line and keybindings.
How do I move the icon in the bar?
omarchy bar move sridhar.layout-presets --section left
I edited the QML and nothing changed. Run omarchy restart shell. The shell
logs a hot-reload line for local plugins but keeps rendering the old code.
Editing bin/omarchy-layout-presets needs no restart.
How do I update or remove it?
omarchy plugin update sridhar.layout-presets
omarchy plugin remove sridhar.layout-presets
Layouts
A layout is a list of relative column widths. The numbers are normalised, so
25-75-25 and 20-60-20 mean the same thing - a wide middle column flanked by
two narrow ones.
A group can hold more than one window, written as the split inside parentheses.
25(50/50)-50-25 is four windows in three columns: two of equal height in the
narrow left column, one large middle window, one tall right window.
The top-level separator picks the axis. - lays the groups out side by
side, / lays them one above the other, and the parentheses always split the
other way. So 50(50-50)/50(33-33-33) is two rows of equal height: two windows
across the top, three across the bottom. Write a layout without spaces.
25-75-25 |
three columns, wide middle |
25-25-25-25 |
four equal columns |
25(50/50)-50-25 |
two stacked left, large middle, tall right |
25-50(50/50)-25 |
two stacked in the middle |
50(50/50)-50(50/50) |
a 2×2 grid |
70-30(33/33/33) |
one large window, three stacked beside it |
50(50-50)/50(33-33-33) |
two across the top, three across the bottom |
50(33-33-33)/50(50-50) |
three across the top, two across the bottom |
50(50-50)/50(25-25-25-25) |
two across the top, four across the bottom |
60/40(33-33-33) |
a master on top, three across the bottom |
70/30(25-25-25-25) |
a tall master, a filmstrip of four beneath |
50/50 |
one above the other, full width |
Row-major layouts are the arrangements a column-major one cannot reach: with two
windows above three, no vertical split runs the full height, so it is not a row
of columns however the columns are subdivided. The reverse also holds - a
uniform grid is reachable either way round, so 50(50/50)-50(50/50) and its
row-major spelling are the same layout, and the panel shows only one of them.
One thing to know when writing your own: surplus windows subdivide a layout's
last slot rather than being spread evenly, so 50/50(50-50) on five windows
gives a bottom row of 50/17/17/17, not four equal windows. Write the layout for
the window count you actually run - 50/50(25-25-25-25) for five - and check
the preview, which draws the real proportions where the label does not.
The panel groups the plain rows of columns under COLUMNS and everything else under STACKED. Which section comes first follows your list: whichever kind you write first is the kind shown first. So to put STACKED on top, lead with a stacked layout:
omarchy bar set sridhar.layout-presets presets \
"25(50/50)-50-25, 50(50-50)/50(33-33-33), 50-50, 25-75-25, 25-25-25-25"
The order applies everywhere, not just on the workspace you set it from, and it deliberately does not depend on the workspace's layout: the panel's order is also the cycle order, and a list that reshuffled per workspace would move every row out from under your muscle memory.
Trimming the list is worth doing at the same time. It shortens the panel and the
cycle together, so the layouts you actually use are the only ones
SUPER+ALT+L steps through.
The panel header names the workspace it is about: how many tiled
windows it holds and which layout it tiles with (4 WINDOWS · DWINDLE LAYOUT).
The layout is not trivia there - it is what decides whether the stacked presets
are offered at all. Edit the set in ~/.config/omarchy/shell.json, on this plugin's
bar entry:
{ "id": "sridhar.layout-presets", "presets": "50-50, 25-75-25, 25(50/50)-50-25" }
When the workspace has a different number of windows than the layout has slots, it stretches to fit. Fewer windows use the leading slots. More windows grow extra columns out of a flat layout, and extra rows in the last column of a stacked one - the direction that layout was already going. Every window always gets a slot.
Because several layouts can stretch to the same thing, the panel hides the
duplicates: at three windows 33-33-33 and 25-25-25-25 are both thirds, so
only the first is listed. The list is therefore shorter on workspaces with
fewer windows - every row you see does something different. A layout hidden
this way is still applied by its twin, and the keybinding for it still works.
Both halves of a panel row describe the stretched result rather than the
layout's raw weights, so what you read is what you get. The label is the split
as percentages of the workspace, with ×N marking a column holding N stacked
windows, and the preview beside it draws the same thing to scale. 25-75-25 on
three windows reads 20 · 60 · 20; on four it reads 20 · 60 · 10 · 10.
Interactions
| Where | Action |
|---|---|
| Bar icon, left click | Open the panel |
| Bar icon, right click | Step to the next layout (the icon pulses to acknowledge) |
| Bar icon, middle click | Step to the previous layout |
| Bar icon, scroll | Cycle - up for next, down for previous |
| Panel, click a row | Apply that layout |
| Panel, ↑/↓ + Enter | Apply the selected layout |
| Panel, Esc | Close |
SUPER+ALT+L / +SHIFT |
Cycle forward / back (whatever you have bound) |
The panel marks the layout the workspace is currently sitting at, so the active arrangement is visible without remembering what was last clicked. Applying a layout the workspace is already in is a no-op by design - nothing on screen moves - so the bar icon pulses on every apply.
The mouse shortcuts cycle rather than re-apply: re-applying the layout already in effect is the one press guaranteed to do nothing, which reads as a broken button. Cycling from the icon and cycling from the keybinding are the same action, so they agree on where you are in the list.
Command line and keybindings
The same action is available over shell IPC, which is what a Hyprland binding should call:
omarchy-shell layouts apply 25-75-25
omarchy-shell layouts apply "25(50/50)-50-25" # quote it: the shell eats ( )
omarchy-shell layouts next # step to the next layout
omarchy-shell layouts previous
omarchy-shell layouts toggle # open/close the panel
The panel footer names both shortcuts: the layout right-click will apply, and
the combo bound to cycling. The combo is read out of ~/.config/hypr/*.lua at
status time, not hardcoded, so it follows a rebind and the line is simply absent
when nothing is bound. (hyprctl binds cannot answer this - under the Lua
config every bind compiles to an opaque __lua reference with no command text,
so the config files are the only place the combo and its command still appear
together.)
next and previous step through the layouts offered on the focused
workspace, without opening the panel. They start from the layout the workspace
is actually in rather than the one last picked, so the cycle follows what is on
screen; layouts hidden as duplicates at the current window count are skipped,
and on a scrolling workspace the stacked ones are left out. The cycle is only
ever as long as there are distinct layouts to reach.
-- ~/.config/hypr/bindings.lua
o.bind("SUPER + ALT + L", "Next layout preset", "omarchy-shell layouts next")
o.bind("SUPER + ALT + SHIFT + L", "Previous layout preset", "omarchy-shell layouts previous")
o.bind("SUPER + SHIFT + C", "Wide middle", "omarchy-shell layouts apply 25-75-25")
bin/omarchy-layout-presets is the backend and works standalone, including on
workspaces that are not currently visible:
omarchy-layout-presets status
omarchy-layout-presets apply 25-75-25
omarchy-layout-presets apply "25(50/50)-50-25"
omarchy-layout-presets apply 33-33-33 --workspace 4
omarchy-layout-presets apply 25-75-25 --no-rebuild
How the arrangement works
Hyprland's dwindle layout has no notion of "columns" - it has a binary tree of splits, and a workspace's windows may well be stacked rather than side by side. So applying a preset is two steps:
-
Rebuild (skipped when the windows are already in the right column/row shape). Every window is floated, which empties the workspace's tiling tree. Each column's first window is then re-tiled behind a
preselect r, building the columns left to right; only once they all exist is each column split downwards withpreselect d. That order matters: splitting a column into rows before the columns to its right exist would nest those windows inside the row split instead of beside it. The original left-to-right, top-to-bottom reading order is preserved. -
Resize. Each window is set to its target size with an exact-size dispatch. A dwindle resize only rebalances the two children of one node, so which sweep order converges depends on the shape of the tree; repeating a sweep in reading order converges for any of them, since sizes that are already correct are no-ops. Targets are computed from what the windows currently add up to, so gaps and reserved bar space need no special handling and the result is exact to the pixel.
Windows stay tiled throughout - floating is only a transient step inside the rebuild, and is undone even if the run fails partway.
Neither step disturbs the pointer. Focusing a window makes Hyprland warp the mouse onto it and the rebuild focuses several, so warping is switched off for the duration and switched back after - the pointer does not dart about and does not end up parked on whichever window was touched last. Keyboard focus does go back to the window that had it.
Install
omarchy plugin add https://github.com/srikat/omarchy-layout-presets.git --enable --yes
omarchy bar move sridhar.layout-presets --section right
Plugins run as unsandboxed code inside omarchy-shell, so omarchy plugin add
lands them disabled by default and asks you to review the code first. Drop
--enable --yes to take that path; it is four small files.
Optional - bind cycling to a key in ~/.config/hypr/bindings.lua:
o.bind("SUPER + ALT + L", "Next layout preset", "omarchy-shell layouts next")
o.bind("SUPER + ALT + SHIFT + L", "Previous layout preset", "omarchy-shell layouts previous")
Pick whatever combo is free on your system - the panel footer reads the binding back out of your config, so it will name the keys you actually chose.
Update or remove it later with omarchy plugin update sridhar.layout-presets
and omarchy plugin remove sridhar.layout-presets.
Hacking on it
Clone it anywhere and symlink the checkout into the shell's plugin path:
ln -sfn /path/to/omarchy-layout-presets ~/.config/omarchy/plugins/sridhar.layout-presets
omarchy-shell shell rescanPlugins
omarchy plugin enable sridhar.layout-presets
Editing Panel.qml needs omarchy restart shell to take effect - the shell
logs a hot-reload line for local plugins but keeps rendering the old code, and
its file watcher does not follow a symlink out of the plugin directory in any
case. Editing bin/omarchy-layout-presets needs no restart.
Single-window workspaces
With one window the presets all collapse to the same thing, so the panel offers the only dimension left: how much of the workspace that window fills. Quick buttons for 50 / 75 / 95 / 100%, and a slider for anything between 20 and 100. The slider applies on release, not while dragging - each change re-tiles the window.
<img src="panel-single.png" alt="The panel on a workspace with one window: a SINGLE WINDOW section with 50/75/95/100% buttons, a slider reading 95%, and a note that the width applies to every workspace with one window until Hyprland reloads." width="330">omarchy-shell layouts single 75
omarchy-layout-presets single 75 --workspace 4
A lone tiled window has no sibling to rebalance against, so a resize dispatch
does nothing to it - its size comes from Hyprland's
layout:single_window_aspect_ratio clamp, which this sets and then re-tiles the
window to pick up. Two consequences the panel states on screen: the clamp is a
global option, so the width applies to every workspace that drops to one
window, and it reverts on a config reload to whatever looknfeel.lua says.
layout:single_window_aspect_ratio_tolerance also discards insets smaller than
its fraction of the screen, so widths close to 100% may snap to full width.
Layouts other than dwindle
Hyprland tracks the layout per workspace - general:layout is only the
default a workspace starts from - so this follows the workspace you are on,
reading its tiledLayout. Switch one workspace to scrolling and the panel
changes there and nowhere else.
On a workspace tiling with scrolling the STACKED section is not shown at all:
a scrolling workspace is a strip of columns, and stacking windows inside one is
not an arrangement it holds on to. The panel notices the switch on its own, and
cycling simply skips them, from the icon and from the keybinding alike.
Stacked layouts remain available over IPC and on the command line, for when you
know what you are asking for.
The column presets do work there. The rebuild is written against dwindle, but it
holds up under scrolling too: floating every window still empties the workspace,
and re-tiling behind a preselect still lands each window where it is wanted.
Two things do differ there and are handled explicitly. A scrolling workspace can put windows of different widths at the same x, so columns are grouped by left edge and width - treating those as one column would report a shape the workspace is not in, skip the rebuild, and leave the preset silently unapplied. And a scrolling strip can be wider than the screen, so the width to divide up is taken from the monitor's tiling span rather than from whatever the windows currently add up to; the gaps between columns are still measured off the live layout, so their exact size never has to be modelled. Under dwindle both come out identical to the old behaviour.
Requirements
Omarchy with the Quickshell-based omarchy-shell, Hyprland 0.56+ (its
hyprctl dispatch arguments are Lua, which is the form this plugin emits), and
Python 3. Written against the dwindle and scrolling layouts; other layouts
are untested.
License
MIT - see LICENSE.