Remote Work Tools

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:

Use tmux if:

Both handle SSH session persistence equally well. The day-to-day experience with either is comparable once you learn the keybindings.

Built by theluckystrike — More at zovo.one