conceptual guide
Zellij — sessions, modes, and persistence
What problem a multiplexer solves
A terminal multiplexer puts a long-lived session process between your terminal emulator and your shells. Your shells talk to the session, the session talks to whatever terminal happens to be attached. Detach the terminal — close wezterm, reboot a flaky SSH connection, switch desks — and the session keeps running. Attach a new terminal and the same shells, the same scrollback, the same layout come back as if nothing happened.
This is different from what i3 and wezterm already give you. i3 manages X windows: when you close a window, the program inside it dies. Wezterm gives you tabs and splits inside one terminal process: when you close that process, the shells inside die with it. A multiplexer is what makes shell state itself outlive the terminal.
| Layer | What it owns | Survives what |
|---|---|---|
| i3wm | Window placement on outputs | Programs surviving X restart? No. |
| wezterm | Terminal tabs & splits inside its process | Closing wezterm? No. |
| zellij | Sessions, tabs, panes inside a session | Closing wezterm, reboots (with resurrection), SSH drops. Yes. |
Sessions, tabs, and panes
A zellij session contains tabs. Each tab contains a tree of panes. Panes are leaves. The tab itself isn't a leaf — it's a labeled root that owns a pane tree. This is similar to i3's tree, but rooted at a session and stopping at the pane (zellij doesn't span monitors).
Floating panes
Inside any tab you can also have floating panes: free-positioned overlays on top of the tiled tree. Toggle the floating layer with Alt+f or in PANE mode with w. A pane can move back and forth: Ctrl+P e embeds a floating pane into the tile tree, and vice-versa.
Pinned panes
A floating pane can be pinned (Ctrl+P i): it stays visible across all tabs in the session. Good for a always-visible system monitor or chat client without forcing it into the tile tree.
Modes — the modal keybind machine
Zellij doesn't use a single "leader key" the way tmux does (Ctrl-b then a letter). Instead, every category of action lives in its own mode, and from NORMAL you switch to one mode at a time. While inside a mode, plain letters do things; press Enter or Esc to return.
This is good because the status bar at the bottom always shows what the keys mean. It's annoying because Ctrl+S and Ctrl+P are also keys editors care about — hence the LOCKED mode escape hatch.
The escape hatch: LOCKED
Some programs — vim, htop, tig, IPython — use the same Ctrl-prefixed keys that zellij claims. Press Ctrl+G and zellij enters LOCKED: every keystroke is forwarded to the running pane untouched. Press Ctrl+G again to come back. The status bar shows a padlock so you don't forget.
Alt+letter aliases. So in practice you can avoid PANE mode entirely most of the time, and only enter modes when you need a less-common action.Layouts are blueprints, not state
A layout file describes the initial structure of a session: tabs, splits, pane sizes, what command runs in each pane. Zellij reads the layout once, when the session is created, then forgets about it. Subsequent edits, splits, and resizes happen on the live session — they don't update the layout file. Re-running zellij --layout dev creates a fresh session at that initial structure.
layout { tab name="code" focus=true { pane size="70%" name="editor" pane split_direction="vertical" { pane name="shell" pane name="logs" command="tail" { args "-F" "/var/log/syslog" } } } tab name="scratch" { pane } }
Key primitives
- pane
- A leaf or a parent. Without children, it runs your shell (or a
command). - split_direction
- On a parent pane, lays children top-to-bottom (
horizontal) or side-by-side (vertical). - size
- Percentage (
"50%") or fixed cells (10). Omit to share remaining space. - command + args
- Run a process instead of the shell. Pane closes when the process exits — unless you also set
close_on_exit false. - cwd
- Working directory for the pane (file path, supports
~). - name
- Label shown on the pane frame and in the pane switcher.
- edit
- Open a file in
$EDITORinstead of running a shell. - floating_panes
- Block containing panes positioned with
x,y,width,height. - pane_template / tab_template
- Define a reusable structure and reference it by name later in the file.
How persistence actually works
There are two distinct things that "survive" with zellij, and confusing them leads to surprises.
1. Detach — the session process keeps running
When you press Ctrl+O d (or your terminal dies and on_force_close "detach" is set), zellij does not stop. It just disconnects the terminal. The zellij server process is still alive, holding your shells, scrollback, layout, and pane state in memory. zellij ls shows it as a live session you can re-attach to instantly.
This is the fast path. It works as long as the machine doesn't reboot.
2. Resurrection — disk-backed sessions
If session_serialization true is in your config, zellij also periodically writes the session's layout to disk under ~/.local/share/zellij/$VER/sessions/. With pane_viewport_serialization true + scrollback_lines_to_serialize N, it also serializes the visible viewport and the last N scrollback lines.
When the actual process is gone (system reboot, OOM kill), zellij attach NAME against a serialized session rebuilds it: same tabs, same pane tree, same scrollback. Running processes are not resurrected — they died with the original process — but your shells start fresh in the right places.
zellij kill-session end the process and (depending on config) leave the on-disk state behind. After that, zellij attach NAME still resurrects the structure. Use zellij delete-session only when you want the state gone for good.Where things live on disk
- ~/.config/zellij/config.kdl
- Main config. Hot-reloaded on save.
- ~/.config/zellij/layouts/
- Your named layouts (
zellij --layout NAMElooks here first). - ~/.config/zellij/themes/
- Custom themes (KDL).
- ~/.local/share/zellij/$VER/sessions/
- Serialized session state for resurrection.
- ~/.cache/zellij/$VER/
- Runtime control sockets and per-session cache. Wiped when a session ends.
- /tmp/zellij-<uid>
- Active IPC sockets — what
zellij lsreads to find running sessions.
Implication: /tmp is volatile
On most distros /tmp is tmpfs and wiped at boot. That's why "active" sessions never survive a reboot — their socket is gone. Serialized state under ~/.local/share/zellij/ does survive, and powers resurrection.
Plugins, briefly
Zellij's UI is largely plugins. The status bar at the bottom, the tab bar at the top, the strider file picker, the session manager — all WASM plugins loaded from zellij:NAME URLs. You can write your own in Rust (or any WASM-compatible language) and load them with file:/path, then pin them as panes in a layout:
pane size=2 borderless=true { plugin location="zellij:tab-bar" } pane { plugin location="file:/home/erick/dev/my-plugin/target/wasm32-wasip1/release/plugin.wasm" }
For day one you don't need this — the bundled plugins are doing a lot of work invisibly. But it's worth knowing the model: the UI is replaceable.
Key conflicts with this stack
Keystrokes flow top-down through your stack: i3wm sees them first, then wezterm, then zellij, then whatever's running inside the pane (nvim, shell). Every layer can swallow a key before it reaches the next.
i3wm — no conflicts
i3 uses Mod4 (Super) as its prefix and never claims plain Ctrl+letter. Even compound shortcuts like Mod4+Ctrl+l only fire on the exact 3-key combo, so they don't shadow plain Ctrl+L.
Wezterm — was eating Ctrl+H and Ctrl+L
The original config bound Ctrl+H / Ctrl+L for tab navigation. Those keys are also nvim's window-direction (<C-w>h shortcut), shell flow-control / clear-screen, and zellij's MOVE-mode entry. They've been moved to Ctrl+Alt+H / Ctrl+Alt+L — a Ctrl+Alt combo that nothing else on this system claims (gsd-media-keys is not running, so its ghost gsettings entries are inert).
Zellij — rebinds for nvim friendliness
Zellij's defaults put mode entry on Ctrl+P (PANE) and Ctrl+O (SESSION). Both are heavily used in nvim: <C-p> is the file picker / FZF idiom, <C-o> is the jumplist. This config moves those to Alt+p and Alt+o so nvim keeps them when running inside a zellij pane.
Zellij tab nav lives on Alt+h / Alt+l (overriding the default MoveFocusOrTab on those keys) so that Ctrl+H / Ctrl+L remain a clean passthrough to nvim window-direction and bash. The MoveFocusOrTab behavior is still available on Alt+arrow keys for pane focus that crosses tab edges.
| Key | Default zellij | This config | Why |
|---|---|---|---|
| Ctrl+P | PANE mode | (unbound) — passes to nvim | nvim file picker |
| Ctrl+O | SESSION mode | (unbound) — passes to nvim | nvim jumplist |
| Alt+p | (unbound) | PANE mode | replacement for Ctrl+P |
| Alt+o | (unbound) | SESSION mode | replacement for Ctrl+O |
| Ctrl+H | MOVE mode | (unbound) — passes through | nvim window-left, bash backspace |
| Ctrl+L | (unbound) | (unbound) — passes through | nvim window-right, bash clear-screen |
| Alt+h | MoveFocusOrTab Left | zellij tab prev | dedicated tab nav (focus moves on Alt+arrow) |
| Alt+l | MoveFocusOrTab Right | zellij tab next | dedicated tab nav |
The LOCKED escape hatch
The remaining defaults (Ctrl+T, Ctrl+S, Ctrl+N, Ctrl+G, Ctrl+Q) still shadow some nvim and shell keys, but they're either rarely-used or the action is more useful at the zellij layer. When you do hit a collision — say, inside htop or a vim plugin that uses Ctrl+T — press Ctrl+G to enter LOCKED and every key after that passes straight through to the inner program. Ctrl+G again to unlock.
One-page summary
- Session = a long-lived zellij process containing tabs and a pane tree per tab.
- Modes are how zellij multiplexes its keybinds — Ctrl+P/Ctrl+T/Ctrl+S open them, Esc closes them, Ctrl+G turns them all off when you need to pass keys through.
- Layouts describe the initial structure; they're consumed once when the session starts.
- Detach (cheap) keeps the process running between terminals. Resurrection (config-gated) rebuilds structure from disk after a reboot.
- UI is plugins. Layout files can pin them as toolbar / sidebar panes.