I used tmux for years. I had a .tmux.conf pushing 300 lines, a custom status bar stitched together with shell scripts, and a plugin manager that I frankly didn’t trust. Every new machine meant wrestling with that config until it felt right again. Then I tried Zellij on a whim and didn’t go back.
This isn’t a "tmux is dead" post. tmux is solid, battle-tested, and works everywhere. But Zellij solves a different set of problems — discoverability, reproducible layouts, and extensibility — in a way that tmux just doesn’t. If you spend meaningful time in the terminal, it’s worth 30 minutes of your time.
Official repo: https://github.com/zellij-org/zellij
What Makes Zellij Different
tmux is a Unix tool. It does one job, composes with other Unix tools, and gets out of the way. That’s a virtue — until you spend an afternoon configuring mouse support that half-works, or you try to share a layout config with a teammate and realize it doesn’t exist as a first-class concept.
Zellij is written in Rust and built around three ideas that tmux never prioritized:
Discoverable keybindings. There’s a permanent status bar at the bottom showing what mode you’re in and what keys do what. You can turn it off, but it means you don’t need to memorize 40 bindings on day one. Newcomers can actually use it without a cheat sheet.
Layouts as real config files. You define your workspace topology in a KDL file, run zellij --layout myproject, and get exactly the panes and tabs you want, with specific commands running in each. It’s reproducible. It’s version-controllable.
A proper plugin system. Plugins are compiled WebAssembly. They run sandboxed, they can render UI, and there’s a growing ecosystem around them.
Installation
Zellij ships prebuilt binaries for Linux and macOS. The fastest path:
# Using the official install script (Linux/macOS)
bash <(curl -L https://zellij.dev/launch)
# Or via cargo if you already have Rust
cargo install --locked zellij
# Homebrew (macOS/Linux)
brew install zellij
# Arch Linux
pacman -S zellij
# NixOS / nix-env
nix-env -iA nixpkgs.zellij
Verify it’s working:
zellij --version
# zellij 0.41.x
Just run zellij with no arguments and you’ll land in a session with the UI explained right there on screen. That’s already different from tmux.
The Config File
Zellij uses KDL (KDL Document Language) for configuration. It looks a bit like CSS-meets-JSON. The global config lives at ~/.config/zellij/config.kdl.
Generate a default config to edit:
zellij setup --dump-config > ~/.config/zellij/config.kdl
Here’s a trimmed-down config with the most useful knobs:
// ~/.config/zellij/config.kdl
// Don't show the tip bar at startup
simplified_ui true
// The default shell to use in panes
default_shell "fish" // or "zsh", "bash", whatever you use
// How long before a session with no clients attached is killed
// Set to 0 for sessions that live forever (good for servers)
session_serialization true
session_name "default"
// Enable mouse support — covered in detail below
mouse_mode true
// Scroll buffer per pane
scroll_buffer_size 50000
// Detach key: Ctrl+q by default, change if it clashes
keybinds clear-defaults=false {
normal {
// Remap prefix to something tmux-like if muscle memory demands it
bind "Ctrl b" { SwitchToMode "Tmux"; }
}
}
// Theme — pick one from the bundled list
theme "catppuccin-mocha"
The session_serialization true line is important on servers — it persists your layout across daemon restarts. Without it, a Zellij upgrade wipes your open sessions.
Layouts: The Feature That Sells It
This is where Zellij genuinely pulls ahead. A layout is a KDL file that describes your session topology: tabs, panes, their sizes, and the commands that run inside them.
Create a layouts directory:
mkdir -p ~/.config/zellij/layouts
A Simple Dev Layout
// ~/.config/zellij/layouts/dev.kdl
layout {
// First tab: your editor + a terminal side by side
tab name="code" {
pane split_direction="vertical" {
// Left pane takes 70% width — your editor
pane size="70%" command="nvim" {
args "."
}
// Right pane: a shell
pane size="30%"
}
}
// Second tab: split horizontally — logs on top, shell on bottom
tab name="logs" {
pane split_direction="horizontal" {
pane size="70%" command="tail" {
args "-f" "/var/log/syslog"
}
pane size="30%"
}
}
// Third tab: just a clean shell
tab name="scratch" {
pane
}
}
Launch it:
zellij --layout ~/.config/zellij/layouts/dev.kdl
Or if you name it exactly dev.kdl and place it in ~/.config/zellij/layouts/, you can reference it by name:
zellij --layout dev
A Docker Compose Monitoring Layout
Here’s a layout I actually use when managing services:
// ~/.config/zellij/layouts/compose-monitor.kdl
layout {
tab name="compose" {
pane split_direction="horizontal" {
// Top: live container logs
pane size="60%" command="docker" {
args "compose" "logs" "--follow" "--tail=100"
}
pane split_direction="vertical" size="40%" {
// Bottom-left: shell in the project dir
pane cwd="/opt/myapp"
// Bottom-right: resource usage
pane command="watch" {
args "-n2" "docker" "stats" "--no-stream"
}
}
}
}
tab name="shell" {
pane cwd="/opt/myapp"
}
}
The cwd key sets the working directory for that specific pane. Combine that with command and you have a fully automated workspace.
Gotcha: Pane Size Math
Zellij doesn’t validate that your sizes add up to 100%. If you set two panes to size="60%" inside the same split, it’ll clip the second one rather than error. Always double-check your splits. Also, omitting size on the last pane in a group makes it take whatever’s left — use that instead of trying to hit exactly 100%.
Mouse Support
Run zellij with the default config and mouse support just works. Click a pane to focus it. Click and drag the dividers between panes to resize. Scroll with the wheel. It handles terminal mouse events natively without the escape sequence gymnastics tmux requires.
The relevant config key:
mouse_mode true
That’s it. There’s no set -g mouse on scattered in three places with conditional version checks.
One real gotcha: if your terminal application (like Neovim or fzf) also captures mouse events, you’ll hit conflicts. Zellij enters scroll mode when you scroll in a pane, which means your app stops seeing the mouse. The fix is the passthrough plugin or — more practically — knowing the Ctrl+s → scroll mode toggle so you can flip in and out deliberately.
For copy-paste with the mouse: hold Shift while selecting to bypass Zellij’s mouse capture and do a raw terminal selection. Same behavior as tmux’s set -g mouse on workaround, just built-in and documented.
Plugins
Zellij plugins are WebAssembly modules. They can render a full UI pane, react to keystrokes, call Zellij’s API to create/close panes, and run with sandboxed permissions. This is miles ahead of tmux’s "write a shell script and pipe it to the status bar" model.
Bundled Plugins Worth Knowing
tab-bar — The tab bar at the top. You already use it.
status-bar — The keybinding hints at the bottom. Disable it once you’ve internalized the bindings:
// In your layout, omit the status bar pane
pane size=1 borderless=true {
plugin location="zellij:status-bar"
}
Just delete that block from your layout to reclaim the line.
filepicker — Opens a pane with a file browser. Bind it:
keybinds {
normal {
bind "Alt f" {
LaunchOrFocusPlugin "zellij:filepicker" {
floating true
}
}
}
}
session-manager — Lists your named sessions, lets you switch between them. Critical if you work across multiple projects:
keybinds {
normal {
bind "Alt s" {
LaunchOrFocusPlugin "zellij:session-manager" {
floating true
}
}
}
}
Third-Party Plugins
The community plugin ecosystem is still young but moving fast. Worth installing:
zjstatus — A fully configurable status bar. Replaces the default one with something you can actually style. Supports git branch display, datetime, custom formatters.
zellij-forgot — A floating keybinding cheat sheet. Shows all your bindings on demand. Useful during the learning curve.
Installing a third-party plugin means downloading the .wasm file and referencing it by path:
// In your config or layout
keybinds {
normal {
bind "Alt z" {
LaunchOrFocusPlugin "file:/home/you/.config/zellij/plugins/zjstatus.wasm" {
floating true
}
}
}
}
Gotcha: Plugin Permissions
When you load a third-party plugin for the first time, Zellij prompts you to grant permissions (read filesystem, open terminals, write to clipboard, etc.). This is the sandbox working correctly. Read what each plugin requests. A tab-bar plugin requesting OpenTerminalsOrPlugins is a red flag.
Keybindings and Modes
Zellij uses a modal system similar to Vim but less aggressive. The modes you’ll use daily:
| Mode | Enter | Purpose |
|---|---|---|
| Normal | default | Moving focus, most actions |
| Pane | Ctrl+p |
Split, close, resize panes |
| Tab | Ctrl+t |
New tab, rename, move |
| Resize | Ctrl+n |
Resize panes with arrow keys |
| Scroll | Ctrl+s |
Scroll pane buffer |
| Session | Ctrl+o |
Detach, switch sessions |
The status bar shows the current mode and available keys. Once you’ve learned the modes, kill the bar and get your screen real estate back.
tmux muscle memory: If you’re deeply wired to tmux’s Ctrl+b prefix, add this to your config:
keybinds clear-defaults=false {
normal {
bind "Ctrl b" { SwitchToMode "Tmux"; }
}
tmux {
bind "\"" { NewPane "Down"; SwitchToMode "Normal"; }
bind "%" { NewPane "Right"; SwitchToMode "Normal"; }
bind "c" { NewTab; SwitchToMode "Normal"; }
bind "n" { GoToNextTab; SwitchToMode "Normal"; }
bind "p" { GoToPreviousTab; SwitchToMode "Normal"; }
bind "d" { Detach; }
}
}
Zellij actually ships a tmux mode for exactly this purpose. It’s not a gimmick — it’s usable.
Running Zellij on a Remote Server
This is where tmux typically dominates — you SSH in, attach to a running session, and pick up where you left off. Zellij does this too.
# On the server — start a named session
zellij --session myproject
# Detach: Ctrl+o, then d
# List sessions
zellij list-sessions
# Reattach
zellij attach myproject
# Kill a session
zellij kill-session myproject
For the session to survive your SSH disconnect, Zellij needs to run as a daemon. It does this automatically as of 0.37 — the server process stays alive when you detach, and zellij attach reconnects to it.
Production-ready tip: On servers I manage, I put the layout in the project repo under .zellij/dev.kdl and alias it:
# ~/.bashrc or ~/.zshrc on the server
alias dev='zellij attach myproject 2>/dev/null || zellij --session myproject --layout /opt/myproject/.zellij/dev.kdl'
Run dev and you either reattach to the existing session or boot a fresh one with your full layout. No more "is there already a session running" guessing.
Gotcha: Session Serialization and Upgrades
When you upgrade Zellij, the running daemon and the new binary can have mismatched protocol versions. You’ll get an error on attach. The fix is to kill the old daemon first:
zellij kill-all-sessions
# Then start fresh
zellij --session myproject --layout /path/to/layout.kdl
This is a real annoyance on automated servers. Until upstream adds seamless upgrade support, keep it in mind before apt upgrade zellij on a box you’re actively using.
What Zellij Still Doesn’t Do Well
Honest assessment, because you’ll hit these:
Scripting and automation. tmux’s send-keys and new-window shell commands are incredibly powerful for scripting. Zellij’s equivalent (zellij action) exists but is less mature. If your workflow involves external scripts driving the multiplexer, tmux still wins here.
Copy mode. Zellij’s scroll/copy mode works, but it lacks tmux’s Vi copy mode depth. Things like visual block selection or complex regex search in the buffer aren’t there yet. Heavy copy-mode users will feel the gap.
Ubiquity. tmux ships with almost every Linux distro. On minimal servers or containers, apt install tmux just works. Zellij requires either a prebuilt binary download or cargo. Not a dealbreaker, but worth knowing.
A Complete Starter Config
Here’s a production-ready starting point you can drop into ~/.config/zellij/config.kdl:
// ~/.config/zellij/config.kdl
simplified_ui true
mouse_mode true
scroll_buffer_size 100000
session_serialization true
copy_on_select true
copy_clipboard "system"
default_shell "bash" // swap to zsh or fish
theme "nord"
keybinds clear-defaults=false {
normal {
// Quick pane splits
bind "Alt |" { NewPane "Right"; }
bind "Alt -" { NewPane "Down"; }
// Float a new pane (great for quick lookups)
bind "Alt n" { NewPane; ToggleFloatingPanes; }
// Navigate panes with Alt+arrow
bind "Alt Left" { MoveFocus "Left"; }
bind "Alt Right" { MoveFocus "Right"; }
bind "Alt Up" { MoveFocus "Up"; }
bind "Alt Down" { MoveFocus "Down"; }
// Session manager
bind "Alt s" {
LaunchOrFocusPlugin "zellij:session-manager" {
floating true
}
}
}
}
The Verdict
Zellij isn’t trying to beat tmux on its own terms. It’s making different tradeoffs: discoverability over minimalism, layouts-as-config over scripted setup, a plugin sandbox over raw shell integration.
For day-to-day development workflows — especially across multiple projects with distinct workspace layouts — Zellij is genuinely better. The layout system alone is worth the switch. Mouse support working by default removes an entire class of configuration pain.
Keep tmux around for containers, minimal servers, and heavy copy-mode work. For everything else, give Zellij a week. The status bar will feel patronizing at first. By day three, you’ll stop noticing it. By day five, you’ll be writing layout files for every project.