Zellij is a terminal multiplexer written in Rust, designed as a more approachable alternative to tmux. It has a built-in UI with visible keybinding hints at the bottom of the screen, sensible defaults that work without a config file, and a plugin system based on WebAssembly.
For remote developers, Zellij offers session persistence like tmux, but with less configuration overhead and a layout system that uses KDL files (a readable config format) instead of shell scripts.
Install Zellij
# macOS
brew install zellij
# Linux — download binary
bash <(curl -L zellij.dev/launch)
# Or via cargo (Rust)
cargo install zellij
# Via apt (Ubuntu PPA)
sudo apt-add-repository ppa:zellij/zellij
sudo apt-get update
sudo apt-get install zellij
# Verify
zellij --version
# zellij 0.40.x or similar
Start a Session
# Start Zellij (creates a default session)
zellij
# Start with a named session
zellij --session work
# List sessions
zellij list-sessions
# or
zellij ls
# Attach to an existing session
zellij attach work
# or
zellij a work
# Kill a session
zellij kill-session work
# or
zellij k work
# Kill all sessions
zellij kill-all-sessions
Default keybindings use Ctrl-g to enter “lock mode” (pass-through for nested terminals). The mode indicator at the bottom shows your current mode: Normal, Pane, Tab, Resize, Scroll, etc.
Zellij Config File
Create ~/.config/zellij/config.kdl:
// ~/.config/zellij/config.kdl
// Use vi-style navigation
default_mode "normal"
// Reduce theme overhead on remote sessions
theme "default"
// Pane frame settings
pane_frames false // hide pane borders (cleaner look)
// Session persistence: sessions survive disconnects
session_serialization true
// Scroll buffer
scroll_buffer_size 50000
// Mouse support
mouse_mode true
// Key bindings — customize to avoid conflicts with nested apps
keybinds clear-defaults=true {
normal {
bind "Ctrl a" { SwitchToMode "Tmux"; } // familiar prefix for tmux users
}
tmux {
bind "[" { SwitchToMode "Scroll"; }
bind "d" { Detach; }
bind "|" { NewPane "Right"; SwitchToMode "Normal"; }
bind "-" { NewPane "Down"; SwitchToMode "Normal"; }
bind "c" { NewTab; SwitchToMode "Normal"; }
bind "n" { GoToNextTab; SwitchToMode "Normal"; }
bind "p" { GoToPreviousTab; SwitchToMode "Normal"; }
bind "h" { MoveFocus "Left"; SwitchToMode "Normal"; }
bind "j" { MoveFocus "Down"; SwitchToMode "Normal"; }
bind "k" { MoveFocus "Up"; SwitchToMode "Normal"; }
bind "l" { MoveFocus "Right"; SwitchToMode "Normal"; }
bind "," { SwitchToMode "RenameTab"; TabNameInput 0; }
bind "r" { SwitchToMode "Resize"; }
bind "z" { ToggleFocusFullscreen; SwitchToMode "Normal"; }
bind "Ctrl a" { Write 1; SwitchToMode "Normal"; } // send literal Ctrl-a
}
scroll {
bind "q" { SwitchToMode "Normal"; }
bind "Ctrl c" { SwitchToMode "Normal"; }
bind "j" { ScrollDown; }
bind "k" { ScrollUp; }
bind "Ctrl d" { HalfPageScrollDown; }
bind "Ctrl u" { HalfPageScrollUp; }
bind "/" { SwitchToMode "EnterSearch"; SearchInput 0; }
bind "n" { Search "down"; }
bind "N" { Search "up"; }
}
resize {
bind "h" { Resize "Increase Left"; }
bind "j" { Resize "Increase Down"; }
bind "k" { Resize "Increase Up"; }
bind "l" { Resize "Increase Right"; }
bind "Esc" { SwitchToMode "Normal"; }
}
}
Layouts
Zellij layouts define how panes and tabs are arranged at startup. Create them in ~/.config/zellij/layouts/:
// ~/.config/zellij/layouts/dev.kdl
// A 3-pane dev layout: editor, terminal, and log viewer
layout {
default_tab_template {
pane size=1 borderless=true {
plugin location="zellij:tab-bar"
}
children
pane size=2 borderless=true {
plugin location="zellij:status-bar"
}
}
tab name="editor" focus=true {
pane split_direction="vertical" {
pane size="65%" focus=true
pane split_direction="horizontal" {
pane
pane size="30%"
}
}
}
tab name="server" {
pane split_direction="horizontal" {
pane command="npm" {
args "run" "dev"
}
pane command="tail" {
args "-f" "logs/app.log"
}
}
}
tab name="git" {
}
}
Start Zellij with a layout:
# Use a specific layout
zellij --layout dev
# Or by file path
zellij --layout ~/.config/zellij/layouts/dev.kdl
# Set a default layout in config.kdl
// default_layout "dev" // add to config.kdl
Auto-Start Layout for Project
Use a shell function that starts or attaches to a Zellij session with the right layout for a project:
# Add to ~/.bashrc or ~/.zshrc
# Start or attach to a project session
dev() {
local project=${1:-$(basename "$PWD")}
local path=${2:-$PWD}
# Check if session exists
if zellij list-sessions 2>/dev/null | grep -q "^$project"; then
echo "Attaching to existing session: $project"
zellij attach "$project"
else
echo "Starting new session: $project"
cd "$path" && zellij --session "$project" --layout dev
fi
}
# Quick alias to attach to last session
alias zj='zellij attach $(zellij list-sessions | head -1 | cut -d" " -f1)'
SSH Persistence with Zellij
Sessions persist on the server across SSH disconnects, just like tmux:
# SSH and attach/create session (same pattern as tmux)
ssh user@server.example.com
# On server: attach or create
zellij attach work 2>/dev/null || zellij --session work
# One-liner SSH + attach from local machine
ssh -t user@server.example.com "zellij attach work 2>/dev/null || zellij --session work"
# ~/.ssh/config: auto-attach on SSH
# Host devserver
# HostName server.example.com
# User ubuntu
# RemoteCommand zellij attach work 2>/dev/null || zellij --session work
# RequestTTY force
Plugins
Zellij plugins extend functionality via WebAssembly. Install community plugins:
# Install zjstatus (a better status bar)
# Plugins live in ~/.config/zellij/plugins/
mkdir -p ~/.config/zellij/plugins
curl -L https://github.com/dj95/zjstatus/releases/latest/download/zjstatus.wasm \
-o ~/.config/zellij/plugins/zjstatus.wasm
Reference in layout:
// In a layout file
pane size=1 borderless=true {
plugin location="file:~/.config/zellij/plugins/zjstatus.wasm" {
format_left "#[fg=#9a8a8a,bold] {session}"
format_right "{datetime}"
format_space ""
datetime_format "%Y-%m-%d %H:%M"
datetime_timezone "America/New_York"
}
}
Zellij vs tmux Decision
Use Zellij if:
- Your team has mixed terminal experience and you want something they can pick up without reading docs
- You want layouts defined as readable config files (KDL) rather than shell scripts
- You prefer Rust tooling and an active development pace
Use tmux if:
- You’re already comfortable with tmux and have existing config you don’t want to rewrite
- You need maximum SSH compatibility on old servers (tmux is ubiquitous, Zellij requires installation)
- You want a larger ecosystem of plugins and resources
Both handle SSH session persistence equally well. The day-to-day experience with either is comparable once you learn the keybindings.
Related Articles
- tmux Config Guide for Remote Developers
- teleport-db-config.yaml
- Best Terminal Multiplexer for Remote Pair Programming
- Quick save script for terminal workflows
- Wezterm vs Alacritty Terminal Comparison: A Practical Guide
Built by theluckystrike — More at zovo.one