Fold a run of bar widgets into one icon that opens them again on click, so a bar full of plugins stays a bar you can read.
Folded, a stack is one icon and a count. Click it and the widgets it holds appear on the bar, in order, behaving exactly as they would have on their own — same components, same settings, same panels. Click again and they go away.
Omarchy's bar builds every widget from a registry of id → Component, and injects exactly three properties into each one: bar, moduleName, and settings. That is the whole contract. A stack reads the same registry, builds from the same components, and injects the same three properties, so a widget inside a stack is not a copy or a proxy of the real one — it is the real one, mounted somewhere else.
~/.config/omarchy/stacks.json:
{
"stacks": [
{ "label": "System", "widgets": [
{ "id": "ianswope.keyring" },
{ "id": "ianswope.timers" },
{ "id": "ianswope.deploys" },
{ "id": "ianswope.snapshots" }
] },
{ "label": "Media", "widgets": [
{ "id": "ianswope.renders" },
{ "id": "ianswope.apple-music" },
{ "id": "ianswope.plex" }
] }
]
}A bare stack object works too, as does a bare array of them — all three shapes are things a person plausibly writes. Each bar entry picks one with stackIndex. An entry may be a plain id string, or an object with its own settings, which are passed to the widget exactly as the bar would pass them:
{ "id": "ianswope.plex", "settings": { "refreshIntervalSec": 30 } }The file is watched, so edits apply without restarting the shell.
A widget in a stack must still be named in shell.json, in the top-level plugins[] array.
Omarchy decides a plugin is enabled by whether its id appears anywhere in shell.json — a bar layout entry, or a plugins[] entry. Take a widget out of bar.layout.right and it is no longer enabled, so the shell never builds its component, so the registry never has it, so a stack has nothing to mount. The symptom is a stack that unfolds into nothing at all, with no error anywhere.
plugins[] is the answer: it marks a plugin enabled without placing it on the bar, which is exactly what a folded widget needs.
{
"bar": { "layout": { "right": [
{ "id": "ianswope.stack", "stackIndex": 0 },
{ "id": "ianswope.stack", "stackIndex": 1 }
] } },
"plugins": [
{ "id": "ianswope.keyring" },
{ "id": "ianswope.timers" },
{ "id": "ianswope.deploys" },
{ "id": "ianswope.snapshots" },
{ "id": "ianswope.renders" },
{ "id": "ianswope.apple-music" },
{ "id": "ianswope.plex" }
]
}Leave a widget in both bar.layout and a stack and it gets built twice — two IPC handlers fighting over one target, two poll timers, two of whatever it does on a schedule. The stack notices that and says so in its tooltip, because nothing else will.
| Setting | Does |
|---|---|
configPath |
Which file to read (default ~/.config/omarchy/stacks.json) |
stackIndex |
Which stack in that file, 0 being the first |
showCount |
Show how many widgets are folded away |
startOpen |
Start unfolded when the shell loads |
allowMultiple is on, so several stacks can sit on one bar, each with its own index.
The count is how many widgets are folded away; it disappears while unfolded, because they are right there. The icon turns urgent when something is wrong — a stack naming a plugin the shell cannot build, an entry that is not a plugin id, or a widget that is also still on the bar. Hover for which.
Requires Omarchy 4 ("Quattro", Quickshell bar).
mkdir -p ~/.config/omarchy/plugins
cp -r omarchy-stack ~/.config/omarchy/plugins/ianswope.stack
# 1. Write ~/.config/omarchy/stacks.json (above)
# 2. Add {"id": "ianswope.stack", "stackIndex": 0} to bar.layout.right
# 3. Move the folded widgets out of bar.layout and into plugins[]
omarchy-restart-shell# Put the folded widgets back in bar.layout.right, remove the stack entries,
# and delete the plugins[] entries you added for them.
rm -rf ~/.config/omarchy/plugins/ianswope.stack
rm -f ~/.config/omarchy/stacks.json
omarchy-restart-shellNothing else is written anywhere. The stack reads shell.json to notice double-mounted widgets and never writes it — the bar owns that file.
node test/model-test.jsCovers the three config shapes, per-widget settings, the double-mount and not-installed checks, and what the tooltip says in each state.
MIT.