Hey everyone — first post here. I’ve been lurking for a while and learning a lot from the discussions. Figured it was time to share what I’ve been working on and get some feedback from people who actually know Zig well.
zt is a terminal emulator I wrote from scratch in Zig. The original goal was to have a usable terminal on a HackberryPi Zero (RPi Zero 2W, 512 MB RAM, 720×720 display) without needing X11 or Wayland. It ended up becoming a more general-purpose terminal that also runs on X11, Wayland, and experimentally on macOS.
Some numbers
Measured on an i5-12450H under X11 (-Doptimize=ReleaseFast):
| zt | Ghostty | Alacritty | st | |
|---|---|---|---|---|
| Startup | 5.6ms | 492ms | 136ms | 52ms |
| Throughput (dense ASCII) | 73 MB/s | 7 MB/s | 19 MB/s | 27 MB/s |
| Idle memory (PSS) | 2.2 MB | 96 MB | 35 MB | 15 MB |
Full benchmark suite and methodology: GitHub - midasdf/zt-bench: Benchmark suite and results for zt terminal emulator · GitHub
Zig-specific things that might be interesting
Comptime backend selection — zt has four backends (framebuffer, X11, Wayland, macOS), but the backend is chosen at comptime via -Dbackend=x11. The compiler eliminates all code paths for unused backends. Same for font data, pixel scale, keyboard layout, and shell path. There’s no runtime config file — you edit config.zig and rebuild (st-style). This means the binary contains exactly what you need and nothing else.
No libc on fbdev — The framebuffer backend uses only std.posix syscalls. No libc, no dynamic linking. The result is a fully static binary that runs on a bare Linux console.
Pure Zig Wayland client — The Wayland backend implements the wire protocol directly without libwayland-client. It handles xdg-shell, wl_shm, text-input-v3 (IME), clipboard (wl_data_device + primary selection), xdg-decoration, and wp_cursor_shape. Only libxkbcommon is linked for keyboard layout support.
SIMD in the VT parser — The bulk ASCII fast path uses @Vector(16, u8) for range checking. Instead of branching per byte, it loads 16 bytes, does a vector comparison against the 0x20–0x7E printable range, and writes directly to the cell array. Dirty regions are marked per-range rather than per-cell.
Row-map scrolling — Instead of memmove-ing the entire cell buffer on scroll, zt uses an indirection table (row map). Scrolling is O(1) — just rotate the row pointers and mark the recycled row dirty. At 60K scrolls, this moves 44 MB of pointers instead of 880 MB of cell data.
What it doesn’t do (yet)
To be upfront about current limitations:
- No scrollback buffer (planned)
- No sixel/image protocol support
- No ligatures (bitmap font only — 59K glyphs are embedded in the binary)
- macOS backend is untested on real hardware (written via
objc_msgSendfrom Zig, without a Mac) - No mouse reporting yet
Build
zig build -Dbackend=x11 -Doptimize=ReleaseFast
Requires Zig 0.15+. Builds in under a second. Cross-compilation to aarch64 works with -Dtarget=aarch64-linux.
I’d appreciate any feedback — on the code, the architecture, the Zig patterns, or anything else. I’m sure there are things I’m doing in non-idiomatic ways. The codebase is ~10K lines across 19 files, so it should be fairly readable.
Thanks for having me here.

