ProductivityPaolo Castro

From Tmux to Zellij - Evolving My Development Workflow

After years of using a custom tmux script for project management, I discovered Zellij. The core workflow stayed the same, but the implementation became simpler and more powerful thanks to native layout configuration.

#zellij#tmux#terminal#workflow#automation

I'm not particularly dogmatic about tools. If something works well for years, great; but I'm always open to trying alternatives when they promise meaningful improvements. In a previous post, I wrote about a shell script I built to automate tmux session management. That setup served me well for a long time, handling dozens of project switches every day with minimal friction.

Then, a couple of months ago, I came across Zellij. At first glance, it looked like another terminal multiplexer trying to reinvent tmux. I was skeptical, tmux works, it's battle-tested, and I had already invested time building tooling around it. But something about Zellij's approach to configuration caught my attention: native layout files written in a clean configuration language.

After trying it for a few weeks, I realized Zellij wasn't just "tmux with a different config format." It solved some of the pain points I had accepted as inevitable, and it made my custom script significantly simpler.

What Stayed the Same

The core workflow concept remained unchanged. I still wanted to type dev my-project and instantly have a pre-configured development environment ready to go. The muscle memory, the command syntax, even the directory structure for storing configurations, all of that stayed exactly the same.

This was important to me. I didn't want to relearn my entire workflow. I wanted the benefits of a better tool without throwing away what already worked.

What Changed: A Much Simpler Script

Here's the new script in its entirety:

hljs bash
#!/bin/sh

#
# Setup Zellij
#
PARAM=${1:-dev}

if [ "${PARAM}" != "." ]; then
  zellij a ${PARAM} || zellij -s ${PARAM} -n "${HOME}/.config/tmux-dev/dev/${PARAM}.kdl"
else
  zellij a ${PARAM} || zellij -s $(basename "${PWD}") -n "${HOME}/.config/tmux-dev/dev/default.kdl"
fi

Compare this to the 40+ lines of tmux scripting. The Zellij version is eight lines, and most of that is comments and whitespace. The logic is simple: try to attach to an existing session, and if that fails, create a new one using the specified layout file.

All the complexity I used to manage manually—creating windows, splitting panes, navigating between them, setting working directories—now lives in Zellij's layout files. Instead of scripting these operations in shell, I declare them in .kdl configuration files that Zellij understands natively.

The Power of Native Layouts

Zellij's layout system is where the real improvement lies. Instead of shell scripts that set environment variables and execute tmux commands in sequence, I now write declarative layouts that describe the desired end state.

Here's my typical layout structure:

hljs kdl
layout {
  cwd "~/code/my-project"

  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="Backend" focus=true split_direction="vertical" {
    pane focus=true
    pane split_direction="horizontal" {
      pane
      pane
    }
  }

  tab name="Frontend" split_direction="vertical" {
    pane
    pane split_direction="horizontal" {
      pane
      pane
    }
  }
}

This creates two tabs. Each tab has a large vertical pane on the left and two smaller horizontal panes stacked on the right. The left pane is where I typically run AI assistants like Claude Code or work in nvim as I'm gradually adopting it as my primary editor, having a full-height terminal makes it easier to follow long conversations, code reviews, or see more of a file at once. The top-right pane runs the development server, and the bottom-right is for ad-hoc commands: git operations, running tests, or installing dependencies.

The layout is consistent and predictable, but now it's also more flexible than what I had with tmux. If a project needs a different arrangement, I just modify the .kdl file. No need to debug shell scripting logic or figure out the right sequence of tmux commands.

Real-World Example

Here's the actual layout I use for this site:

hljs kdl
layout {
  cwd "~/Developer/devtimestories"

  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="Homepage" cwd="homepage" focus=true split_direction="vertical" {
    pane focus=true
    pane split_direction="horizontal" {
      pane
      pane
    }
  }

  tab name="DTS" split_direction="vertical" {
    pane
    pane
  }
}

The "Homepage" tab is where I spend most of my time. The left pane is for editing in nvim or running Claude Code, while the right side has npm run dev in the top pane and a general-purpose shell in the bottom. The "DTS" tab is simpler—just two vertical panes for any root-level operations or documentation work.

One of Zellij's strengths is its navigation model. I use Option + Arrow Keys to move between panes in any direction. If I press Option + Right at the rightmost pane, it cycles to the next tab. Press it again at the last tab, and it wraps back to the first. This makes navigation feel seamless—I don't think about "switching tabs" versus "switching panes." I just move in the direction I want to go.

There are probably more efficient keybindings buried in Zellij's configuration, but this setup is intuitive enough that I don't have to remember complex shortcuts. The cognitive overhead is minimal, which is exactly what I want from a tool like this.

Trade-offs and Considerations

Switching to Zellij wasn't without trade-offs. The biggest one is maturity: tmux has been around for over a decade and is installed on virtually every server I've ever SSH'd into. Zellij is newer, and while it's stable for daily use, it doesn't have the same ubiquity. If I need to work on a remote machine, I still fall back to tmux.

There's also the learning curve of .kdl syntax, though I found it straightforward. If you're already comfortable with declarative configuration formats like YAML or TOML, KDL will feel familiar.

Finally, plugin support and ecosystem tooling are still evolving. Tmux has a massive collection of plugins built over years. Zellij's plugin system is newer, though the core functionality has been solid enough that I haven't felt the need for third-party extensions.

Why It Worked for Me

What made this transition successful was that I didn't try to rewrite everything at once. I kept the same command (dev my-project), the same directory structure for configurations, and the same mental model of "one command to load an environment." I just swapped out the underlying implementation.

The result is that my workflow feels the same, but the maintenance burden dropped significantly. When I need to adjust a layout, I edit a declarative file instead of debugging shell script logic. When I add a new project, I copy an existing .kdl layout and tweak a few paths. It's faster, clearer, and less error-prone.

Conclusion

I'm not advocating that everyone abandon tmux for Zellij. Tmux is a proven tool that will continue to work reliably for years. But if you've built custom scripting around tmux and find yourself fighting with layout management or window positioning, it's worth taking a look at Zellij's native layout system.

For me, the transition was smooth precisely because I had already thought through what I wanted from a development environment manager. The tmux script taught me what mattered: fast context switching, predictable layouts, and minimal cognitive overhead. Zellij just happened to offer a better way to implement those requirements.

Want to read more?

View All Stories