Zellij

Revision as of 14:14, 9 June 2026 by Debtquity (talk | contribs) (zellij: init)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Zellij is a terminal multiplexer: it enables a number of terminals (or windows), each running a separate program, to be created, accessed, and controlled from a single screen. Zellij may be detached from a screen and continue running in the background, then later reattached.

Installation

Shell

To temporarily use zellij in a shell environment without modifying your system configuration, you can run:

$ nix-shell -p zellij

This makes the zellij available in your current shell. You can then launch zellij by typing zellij.

Configuration

zellij can be configured using Home Manager:

As an example:

❄︎ /etc/nixos/home-configuration.nix
programs.zellij = {
  enable = true;
  # automatically start zellij when opening a new terminal
  enableBashIntegration = true;
  enableFishIntegration = true;
  enableZshIntegration = true;

  # zellij uses KDL as its configuration language,
  # see also https://zellij.dev/documentation/configuration.html
  settings = {
    theme = "custom";
    themes.custom.fg = "#ffffff";
    keybinds._props.clear-defaults = true;
    keybinds.pane._children = [
  };
  # extra settings as a raw string
  # they are appended to the end of the file
  extraConfig = ''
    keybinds {
        pane {
            bind "h" "Left" { MoveFocus "Left"; }
            bind "l" "Right" { MoveFocus "Right"; }
            bind "j" "Down" { MoveFocus "Down"; }
            bind "k" "Up" { MoveFocus "Up"; }
        }
    }
  '';
};

Using Plugins

Zellij offers a WebAssembly / WASI plugin system, allowing plugin developers to develop plugins in many different languages. Plugins can be configured using programs.zellij.plugins. In nixpkgs, they can be found in the zellijPlugins namespace.

❄︎ /etc/nixos/home-configuration.nix
programs.zellij = {
  enable = true;
  plugins = with zellijPlugins; [ vim-zellij-navigator ];
  settings ={
    plugins = {
      vim-zellij-navigator = {
        vim_commands = "v|nvim|vim|testNvim";
      };
    };
  };
  extraConfig = ''
    keybinds {
        shared_among "normal" "locked" "scroll" {
            bind "Ctrl j" {
                MessagePlugin "vim-zellij-navigator" {
                    name "move_focus";
                    payload "down";
                };
            }
        }
    }
  '';
};

This will generate ~/.config/zellij/config.kdl, with something like this:

≡︎ ~/.config/zellij/config.kdl
load_plugins {
	vim-zellij-navigator
}
plugins {
	vim-zellij-navigator location="file:/home/perchun/.config/zellij/plugins/vim-zellij-navigator.wasm" {
		vim_commands "v|nvim|vim|testNvim"
	}
}

// extraConfig

keybinds {
    shared_among "normal" "locked" "scroll" {
        bind "Ctrl j" {
            MessagePlugin "vim-zellij-navigator" {
                name "move_focus";
                payload "down";
            };
        }
    }
}

Packaging Plugins

All plugins are packaged in the pkgs/by-name/ze/zellij/plugins/ folder. Currently, only Rust plugins are packaged, but if you figure out how to package other languages, contributions are welcome.

To add a new plugin, you can just use nix-init like this:

≡︎ 
$ nix-init pkgs/by-name/ze/zellij/plugins/rust/my-plugin-name.nix
Enter url
❯ https://github.com/hello/my-plugin-name
Enter tag or revision (defaults to v0.1.0)
❯ v0.1.0
Enter version
❯ 0.1.0
Enter pname
❯ my-plugin-name
How should this package be built?
❯ buildRustPackage - cargoHash

If the repository name starts with zellij, you should omit it in the actual package name. zellijPlugins.autolock looks better than zellijPlugins.zellij-autolock.

After you have generated the initial definition file, remove all dependencies that were automatically set by nix-init. They are not needed when compiling a WASM binary. If the plugin depends on something at runtime, read the next section.

Specifying runtime dependencies

Runtime dependencies are packages that will be used by the plugin inside a Zellij session. Those are specified in passthru.runtimeDeps attribute from pkgsBuildBuild attrset.

Since we compile all plugins for WASI, everything that the plugin gets as derivation arguments is also compiled for WASI. Some packages (for example coreutils) might not be available on WASI, so we need to use pkgsBuildBuild attrs set (which points to the user's system).

❄︎ pkgs/by-name/ze/zellij/plugins/rust/jbz.nix
# assume we build the plugin on an x86_64-linux machine
{
  lib,
  fetchFromGitHub,
  rustPlatform,

  # these will be compiled for WASI, not x86_64-linux!
  just,
  bacon,
  # but pkgsBuildBuild points to x86_64-linux
  pkgsBuildBuild,
}:
rustPlatform.buildRustPackage (finalAttrs: {
  pname = "jbz";
  version = "0.39.0";

  src = fetchFromGitHub {
    owner = "nim65s";
    repo = "jbz";
    tag = "v${finalAttrs.version}";
    hash = "sha256-3n3Bv3YDb1+MYJTTAmMkIgGY7kX9IVUoDNV4c/n0Ydo=";
  };

  cargoHash = "sha256-U+P2LlhmXwaZy2a2eigrg545HTuV1T01jZfUOEUQ5+w=";

  # specify runtime dependencies here
  passthru.runtimeDeps = with pkgsBuildBuild; [
    bacon
    just
  ];

  meta = {
    description = "Display your Just commands wrapped in Bacon";
    homepage = "https://github.com/nim65s/jbz";
    changelog = "https://github.com/nim65s/jbz/releases/tag/${finalAttrs.src.tag}";
    license = lib.licenses.mit;
    maintainers = with lib.maintainers; [ you ];
  };
})

If you are wondering why pkgsBuildBuild is named like that, refer to the docs on cross-compilation.

Overriding plugins

Plugins use a wrapper for additional post-processing, like stripping unnecessary information from pluginDrv.name. So naїve override won't work, as it overrides the wrapper and not the plugin itself. Instead, you should override the unwrapped version and then pass that to the wrapper:

❄︎ 
final: prev: {
  zellijPlugins = prev.zellijPlugins // {
    # wrap our overrode package
    zjstatus = prev.zellijPlugins.wrapper "zjstatus" (
      # override unwrapped package
      prev.zellijPlugins.zjstatus.unwrapped.overrideAttrs (
        oldAttrs:
        let
          src = final.fetchFromGitHub {
            owner = "dj95";
            repo = "zjstatus";
            rev = "17609e498f88e674b846b844b48ad1dc545fddcf";
            hash = "sha256-/LsFGF0BAYdiVVQZ/8vjo6IXuSsYS+ueIWHN47NHEAc=";
          };
        in
        {
          version = "0-unstable-2026-06-03";
          inherit src;

          # override cargoHash if it is needed
          cargoDeps = final.rustPlatform.fetchCargoVendor {
            inherit src;
            hash = "sha256-+UYNIPrRYUYwxHk227PDo5vdAqho8SMm80o7UqSdZ3g=";
          };
        }
      )
    );
  };
}

See also