Zellij: The tmux Replacement You Didn’t Know You Needed

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.

Leave a comment

👁 Views: 2,289 · Unique visitors: 1,646