ztheme
ztheme is a fast asynchronous Zsh prompt. It renders the directory, command
status, and prompt character immediately, then streams Git and runtime context
into the active prompt.
It has one user-local daemon, no Git writes or implicit fetches, and a typed theme format without plugins or an interpreted template language.
Installation
ztheme supports Zsh on macOS and Linux. The default theme uses Nerd Font
glyphs. Building from source requires Rust 1.97 or newer; managed dependency
setup requires curl, tar, and shasum or sha256sum.
Install from crates.io
cargo install ztheme --locked
ztheme setup
Build from source
git clone https://github.com/alexykn/ztheme.git
cd ztheme
cargo install --locked --path . --root ~/.local
ztheme setup
Ensure ~/.local/bin is on PATH, then add the embedded integration to
.zshrc:
eval "$(ztheme init zsh)"
Release archive
Tagged releases provide native archives for Apple Silicon, Intel macOS, and
x86_64 or arm64 Linux. Download the archive matching your platform from
Releases, verify it against
SHA256SUMS, and extract it. Each archive contains ztheme, LICENSE, and
README.md. Place ztheme in a directory on PATH, then run ztheme setup.
ztheme setup downloads checksum-verified copies of gitstatusd and, when
selected, zsh-syntax-highlighting and zsh-autosuggestions. Normal prompt use
does not make network requests or send telemetry.
Updating and removing
For a source installation, pull the repository and repeat the install command.
For a release archive, replace the installed executable with a newer verified
archive. ztheme clear removes cached prompt data but preserves managed helper
installations. To remove ztheme completely, remove the executable and the
${XDG_CACHE_HOME:-$HOME/.cache}/ztheme,
${XDG_DATA_HOME:-$HOME/.local/share}/ztheme, and
${XDG_CONFIG_HOME:-$HOME/.config}/ztheme directories if you no longer need
their cache, managed helpers, or themes.
Shell defaults
The integration establishes a complete interactive baseline once per shell.
It enables persistent deduplicated shared history, directory-stack navigation,
interactive comments, cached completion with a daily audit, hidden-file and
case-insensitive completion, prefix-aware history search, and common terminal
navigation keys. It does not change PATH, aliases, application environment,
editor selection, terminal integration, or external tool initialization.
A nonstandard HISTFILE makes the complete history setup user-owned. The
standard ${ZDOTDIR:-$HOME}/.zsh_history still receives ztheme's defaults
because macOS assigns that path with a small system history. Set
ZTHEME_HISTORY_DEFAULTS=0 before initialization to retain custom history
settings that use the standard path. Existing completion initialization and
completion styles are preserved. ztheme sets VIRTUAL_ENV_DISABLE_PROMPT=1
only when the variable is unset.
Defaults are ordinary Zsh state. Assignments after initialization take precedence and survive theme reloads and deferred input-plugin initialization:
HISTSIZE=50000
Set ZTHEME_SHELL_DEFAULTS=0 before initialization to retain the prompt,
themes, Git integration, syntax highlighting, and autosuggestions without the
shell baseline:
ZTHEME_SHELL_DEFAULTS=0
Themes
Theme selection is separate from presentation:
# ${XDG_CONFIG_HOME:-$HOME/.config}/ztheme/config.toml
= 1
= "alx"
A named theme is loaded from
${XDG_CONFIG_HOME:-$HOME/.config}/ztheme/themes/alx.toml. Absolute theme paths
are also accepted. Use default or omit the config file to select the embedded
default theme. A one-shell override is available during initialization:
eval "$(ztheme init zsh --theme /absolute/path/to/theme.toml)"
Manage themes from an initialized shell:
ztheme theme list
ztheme theme edit vesper
ztheme theme apply vesper
ztheme theme reload
theme list always shows the embedded default first, followed by custom themes
from ${XDG_CONFIG_HOME:-$HOME/.config}/ztheme/themes. It renders each theme's
semantic palette, layout, Git symbols, and a representative prompt example.
ANSI colors are emitted only to a terminal and are disabled when NO_COLOR is
set.
theme edit opens the selected external theme with nonempty $VISUAL, falling
back to nonempty $EDITOR, waits for the editor, and validates the result.
The embedded default is read-only. Editor values may contain arguments, such
as VISUAL="code --wait".
theme apply validates and compiles the theme before atomically updating
config.toml, then evaluates the generated integration in the current shell.
theme reload rereads the exact selector active in the current shell without
changing config.toml; it is intended for the edit-and-reload loop. Both
commands leave the current prompt unchanged if theme compilation fails.
Custom themes overlay the embedded defaults, so they only need to contain the values they change:
= 1
[]
= "#89b4fa"
= "#7f849c"
= "#cba6f7"
= "#f9e2af"
= "#a6e3a1"
= "#fab387"
= "#f38ba8"
[]
= [
["directory", "git", "python"],
["character"],
]
= ["status"]
= " "
= true
[]
= "on "
= " "
[]
= "!"
= "✘"
= "?"
[]
= "via "
= " "
= "v"
= { = "accent" }
Available segments are directory, git, character, status, python,
perl, java, kotlin, scala, rust, go, bun, deno, node,
ruby, php, dotnet, c, cpp, swift, and lua.
Git and runtime segments are asynchronous and therefore left-prompt only.
directory and status may be placed in the final-line right prompt.
character, when present, must be the final segment of the final left line.
Segments cannot be repeated. The separator is inserted only between nonempty
segments.
Colors are either palette names or direct #RRGGBB values. Styles support
foreground and background colors, bold, underline, and standout. Theme
literals are escaped rather than interpreted as Zsh prompt code.
Input styling is inherited from the embedded default theme and is entirely optional in custom themes. Override only the entries that should differ:
[]
= { = "muted" }
= { = "accent" }
[]
= { = "success", = true }
= { = "text" }
= { = "text" }
= { = "text" }
= { = "muted" }
The keys under input.syntax map directly to documented
ZSH_HIGHLIGHT_STYLES names. Styles use the same typed fields as prompt
segments; "none" explicitly disables a syntax style. The embedded
themes/default.toml contains the complete mapping, including optional
bracket, cursor, line, and root highlighters. Pattern and regular-expression
highlighters use separate Zsh pattern maps and are not theme syntax styles.
The input styles are applied through
zsh-syntax-highlighting.
Command suggestions are provided by
zsh-autosuggestions.
ztheme setup offers to install pinned, checksum-verified copies under
${XDG_DATA_HOME:-$HOME/.local/share}/ztheme/zsh-syntax-highlighting
and ${XDG_DATA_HOME:-$HOME/.local/share}/ztheme/zsh-autosuggestions.
The shell integration loads both once, after .zshrc has finished and before
the first interactive line. If another copy is already loaded, ztheme leaves
it alone.
Autosuggestions use their asynchronous mode and documented manual-rebind mode,
so widget wrapping happens once rather than during every prompt. Right Arrow,
End, and Ctrl-Space accept the current suggestion. Missing or declined input
helpers never prevent the prompt from starting and add no recurring prompt
work. Default key assignments are made before the deferred widget wrapping, so
later bindkey commands remain authoritative.
Every segment supports bounded unstyled outer spacing:
[]
= " "
= " "
= { = 0, = 1 }
prefix and suffix remain inside the segment style, which makes them useful
as colored padding. spacing.before and spacing.after add zero to sixteen
ordinary spaces outside the style reset. They default to zero.
TOML is read, merged, and validated only by ztheme init zsh. Rust then
compiles an exact unrolled left/right compositor and a compact asynchronous
theme value. Prompt refreshes do not read or parse theme files. Re-run the
initialization, source .zshrc, or start a new shell after changing a theme.
Git status is provided exclusively by
gitstatusd. On first initialization,
ztheme checks its fixed managed path and, when missing, offers to install the
pinned upstream binary. Installation downloads the official artifact, verifies
its SHA-256 checksum, and stores it under
${XDG_DATA_HOME:-$HOME/.local/share}/ztheme/gitstatus/v1.5. For unattended
setup of gitstatusd and both input helpers, run:
ztheme setup --yes
The normal initialization path performs no platform detection, version process, checksum, Homebrew lookup, or network work once that executable exists.
The public CLI exposes initialization, dependency setup, help, and
ztheme clear. The shell integration uses hidden commands for asynchronous
context updates and daemon startup.
A production shell always uses /tmp/ztheme-$UID/daemon.sock, regardless of
which binary path initialized it. A process-lifetime advisory lock guarantees
that only one production daemon can own that socket; the operating system
releases the lock immediately if the daemon exits or crashes. The next daemon
then replaces any stale socket before binding.
Shell initialization starts a daemon contender directly in the background. There is no startup ping or version handshake: an existing daemon keeps the singleton lock and the contender exits. Every normal cache or Git request already carries the wire version in its fixed five-byte header. When a newer client reaches an older daemon, that request makes the daemon shut down cleanly; the client starts its replacement and retries the request. Older clients cannot terminate newer daemons.
Additional instances require an explicit development name:
eval "$(target/release/ztheme init zsh --dev feature)"
target/release/ztheme clear --dev feature
Each development name has its own singleton socket. Without --dev NAME, even
a development build joins the production instance.
The daemon keeps up to 500 semantic runtime snapshots in memory and persists them under
${XDG_CACHE_HOME:-$HOME/.cache}/ztheme. It binds the socket before loading
that file in the background, writes changes atomically after a short debounce,
and expires entries after 24 hours unless their project, environment, selector,
or executable fingerprint changes sooner. Access updates LRU order
without extending that safety expiry.
The same daemon owns one persistent gitstatusd child. Prompt clients never spawn
git and ztheme does not keep a second Git cache. ztheme clear discards the
runtime cache and restarts gitstatusd, while preserving the installed
executable.
The asynchronous snapshot process receives the precompiled theme value, formats
finished Git/runtime fragments in Rust, and streams only those fragments to
Zsh. The daemon remains theme-independent, and dynamic values are sanitized and
escaped for safe use with Zsh PROMPT_SUBST.
Upstream protocol boundaries are explicit:
- bare repositories are not reported by gitstatusd
GIT_DIRis supported, but combining it withGIT_WORK_TREEis rejected- gitstatusd reports renames as deleted plus new, so ztheme does not render a separate rename marker
gitstatusd is distributed under GPL-3.0. ztheme communicates with it as a separate process through its documented pipe protocol.