A zellij plugin that replaces the thin one-row tab bar with a taller, multi-row tab bar in which every tab is drawn as a color-coded minimap of its own pane layout — a tiny pixel-grid thumbnail of how that tab's terminal is split. Panes are identified by color; where a tab is wide enough, a summarized title is overlaid; the ⌘N switch hint is shown per tab.
The renderer rendered standalone in a terminal: five sample layouts (a single pane, a 2-column split, a 2-row split, a 2×2 grid, and a main+stack) shown as color-coded minimaps — pixel-only on top, with overlaid labels below at two widths. This renderer is now wired into the live zellij tab bar, including click-to-switch, and shipped in
v0.1.0as a prebuilt wasm (see Status).
Box-drawing rules can only place a line on a cell boundary. The upper-half-block glyph ▀ paints its foreground color on the top half of a cell and its background color on the bottom half, so the color can change within a single cell. That doubles the vertical resolution (a 3-text-row block becomes a 6-pixel-tall grid) and lets even finely split layouts render as distinct color bands instead of collapsing into noise. It's the same half-block technique image-to-terminal tools (chafa, timg) use, applied to a pane map.
3 text rows left A (full height) right: top B / bottom C, split by ▀
row 1 │ █ A █ │ ▀▀▀ fg=B bg=B (top & bottom both B)
row 2 │ █ A █ │ ▀▀▀ fg=B bg=C (top half B / bottom half C — split mid-cell)
row 3 │ █ A █ │ ▀▀▀ fg=C bg=C (top & bottom both C)
A focused pane is marked with an outline ring and a bold label — its fill keeps the same identity hue as when unfocused, so a pane never changes color as focus moves. The ring is a luminance-shifted shade of the pane's own fill (a blue pane gets a slightly different blue outline), so the highlight stays in the pane's hue family. Titles degrade gracefully — labels that cannot fit are dropped rather than truncated into noise.
🚧 Early development.
- ✅ The minimap renderer (
src/minimap.rs) is complete and unit-tested (HSL palette, half-block grid, focus ring, label degradation). It has no zellij dependency, so it runs and is tested on the native host. - ✅ The full render pipeline is wired: every tab is projected from zellij's live
PaneManifest, packed into column spans (src/line.rs), assembled into a per-tab block at its budgeted width (src/tab_block.rs), and composed into the multi-row bar (src/paint.rs). By default the active tab is centered, so the strip slides to follow focus; setalign "left"to anchor the row instead. Tabs that don't fit collapse into← +N/+N →end markers. - ✅ Mouse click-to-switch is wired: a left click anywhere inside a tab's column span focuses that tab. The hit-test (
src/line.rs) maps the clicked column to the tab drawn there and converts its 0-based position to the 1-based indexswitch_tab_toexpects, so it needs theChangeApplicationStatepermission (see the first-run note below). - ✅
v0.1.0is published with a prebuiltzellij-tabmap.wasmasset, so you can wire the plugin in by URL without building it — see Use it in zellij.
The full design — architecture, rendering pipeline, degradation ladder, golden-repo mapping, risks, and test strategy — lives in docs/design.md.
rustup target add wasm32-wasip1 # one time
cargo build --release # .cargo/config.toml targets wasm32-wasip1
# artifact: target/wasm32-wasip1/release/zellij-tabmap.wasmIn your layout's default_tab_template, give the tab-bar pane a height of 3 rows and point it at the hosted release artifact. zellij fetches and caches the wasm on first use — no clone, no build:
default_tab_template {
pane size=3 borderless=true { // 1 → 3 rows
plugin location="https://github.com/GeneralD/zellij-tabmap/releases/latest/download/zellij-tabmap.wasm" {
shortcut_prefix "⌘"
active_width "24"
align "center" // "center" slides to keep the active tab centered; "left" anchors the row (all-fit only)
reorder "false" // drag a tab to reorder; "true" also needs RunActionsAsUser
tab_gap "2" // cleared columns between tab blocks; "0" packs them flush
gradient "sheen" // pane fill sweep: "sheen" (L→R, default) / "weave" (alternating rows) / "off" (flat)
inactive_dim "true" // dim inactive tabs so the active one stands out; "false" to opt out
}
}
children
pane size=1 borderless=true { plugin location="status-bar" }
}The hosted URL above always tracks the newest release. To pin a specific version instead, swap it for the per-tag form — e.g. https://github.com/GeneralD/zellij-tabmap/releases/download/v0.1.0/zellij-tabmap.wasm.
Contributors hacking on the plugin can build from source and point at the local artifact instead:
plugin location="file:/absolute/path/to/zellij-tabmap.wasm"
align— center vs left. When every tab fits,aligndecides how the row is anchored:center(default) re-centers the active block on each focus change, so the whole strip slides horizontally;leftpins the row's left edge at the start of the tab area (column 0, or just after any reserved prefix columns), removing that whole-strip slide. Noteleftdoes not freeze every tab's column — the active tab is still drawn wider than the inactives, so the tabs drawn after it shift right as focus crosses them; only the leftmost tab is truly fixed.aligngoverns the all-fit case only — when tabs overflow, the visible window always follows the active tab (with← +N/+N →markers) regardless ofalign, because the active tab must stay on screen. The default stayscenterso existing layouts render unchanged on update.
tab_gap— space between tabs. Leaves the given number of cleared columns between adjacent tab blocks so the boundary between screens reads clearly (default2). Set0to pack the blocks flush.
gradient— per-pane fill sweep.sheen(default) sweeps each pane block's fill left-to-right from its base color toward a luminance-shifted shade (lighter for dark themes, darker for light ones);weavealternates the sweep direction on each half-block pixel row for a woven texture. The focus ring, labels, and the⌘Nbadge stay solid on top, so readability is unchanged. Setofffor flat fills.
inactive_dim— visual cue for the active tab. Whentrue(default), inactive tabs are dimmed toward the terminal background so the active tab stands out clearly: its pane fills stay vivid, its shortcut badge and focused pane label are drawn in white, and no focus ring appears on other tabs. Setfalseto disable the dimming and treat all tabs with equal intensity.First-run permission note: the bar needs two permissions —
ReadApplicationState(pane/tab layout data) andChangeApplicationState(click-to-switch) — but when loaded fromdefault_tab_templateit gets no usable permission prompt and appears inert on first launch (see the upstream issue zellij#4982, which tracks this dead-end for background plugins). To grant the permissions, load the plugin once in a regular pane — the bar stays selectable until the permission flow resolves, so the prompt can be focused and answered there, and the grant is cached per URL:zellij plugin -- https://github.com/GeneralD/zellij-tabmap/releases/latest/download/zellij-tabmap.wasmPress y to accept and close the pane, then restart the session so the template-loaded bar picks up the grant. Note the grant is keyed on the exact plugin URL, so a version-pinned URL needs this once per version, while the
latestURL needs it only once (at the cost of zellij's URL-keyed wasm cache holding back updates). As a fallback you can also add the entry by hand topermissions.kdlin zellij's cache directory (Linux:~/.cache/zellij/permissions.kdl, macOS:~/Library/Caches/org.Zellij-Contributors.Zellij/permissions.kdl) — the file is read once at server startup, so manual edits take effect only in a fresh session.Enabling
reorderrequests a third permission,RunActionsAsUser(for theMoveTabByTabIdaction a tab drag performs). Granting is all-or-nothing for tab-template plugins, so when you setreorder "true"you must grant all three permissions and reload — otherwise the bar freezes with no prompt. Left at the default (false), the plugin requests only the two permissions above, so an existing install keeps working unchanged across updates.
cargo test --lib --target "$(rustc -vV | sed -n 's/host: //p')" # native unit tests
cargo clippy --target wasm32-wasip1 --all-features --lib # lint (CI denies warnings)
cargo build --release --target wasm32-wasip1 # the loadable wasmCI runs the same three on every push; tagging vX.Y.Z builds the wasm, generates a changelog with git-cliff, and attaches the artifact to a GitHub Release.
Structured after KiryuuLight/zellij-attention, used as a golden-repository reference for the Rust/WASM zellij-plugin layout (thin register_plugin! bin + native-testable lib + FFI-stubbed tests + CI/release workflows).

