shui = Shell UI. ζ°΄ = water in Chinese β fluid, effortless, takes the shape of its container.
Most Zsh scripts scatter raw echo -e "\033[32m..." calls everywhere. shui gives you a proper design system instead β semantic components, a token-based theme engine, and a single consistent API.
One file to source. No dependencies. Pure Zsh.
Examples below use
SHUI_ICONS=emojiβ works everywhere without a Nerd Font. Swap toSHUI_ICONS=nerdfor richer glyphs if you have one installed.
git clone https://github.com/kud/shui ~/.shuigit submodule add https://github.com/kud/shui lib/shuiAdd to your .zsh_plugins.txt:
kud/shui
Then reload:
antidote loadzinit light kud/shuiClone into your custom plugins directory:
git clone https://github.com/kud/shui ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/shuiThen add shui to the plugins array in ~/.zshrc:
plugins=(... shui)zplug "kud/shui"source ~/.shui/shui.zsh
shui success "Deployment complete"
shui error "Build failed"
shui warning "Deprecated flag used"
shui info "Running in dry-run mode"β
Deployment complete
β Build failed
β οΈ Deprecated flag used
βΉοΈ Running in dry-run modeSource shui at the top of any Zsh script:
#!/usr/bin/env zsh
source "${0:A:h}/lib/shui/shui.zsh"${0:A:h} is the Zsh idiom for the absolute directory of the current script β the path resolves correctly regardless of where you call your script from.
Or use an environment variable for a globally installed shui:
# ~/.zshrc
export SHUI_DIR="$HOME/.shui"
# your-script.zsh
source "$SHUI_DIR/shui.zsh"To select a theme before loading:
SHUI_THEME=minimal source "$SHUI_DIR/shui.zsh"The four semantic message types. Each prints an icon and coloured text on its own line.
shui success "Deployment complete"
shui error "Build failed: config not found"
shui warning "API key expires in 3 days"
shui info "Running in dry-run mode"β
Deployment complete
β Build failed: config not found
β οΈ API key expires in 3 days
βΉοΈ Running in dry-run modeInline text formatting and semantic colour helpers.
shui bold "Bold text"
shui dim "Dimmed text"
shui italic "Italic text"
shui underline "Underlined text"
shui text --color=success "Success colour"
shui text --color=muted "Muted colour"Bold text
Dimmed text
Italic text
Underlined text
Success colour
Muted colourAvailable colour types: success error warning info primary muted accent
Structure your script output with sections, headings, and spacing.
shui section "Setup"
shui subtitle "Installing packages"
shui subsection "npm dependencies"
shui subsection "brew formulae"
shui divider
shui spacer
shui spacer 3Setup
β Installing packages
β’ npm dependencies
β’ brew formulae
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββSolid background inline label. Writes to stdout without a newline β use inside $(...).
echo "Version: $(shui badge success v2.0)"
echo "Build: $(shui badge error FAIL) $(shui badge success PASS) $(shui badge muted SKIP)"Version: v2.0
Build: FAIL PASS SKIPshui badge <type> <text>Available types: success error warning info primary muted
Rounded-edge inline tag. Writes to stdout without a newline β use inside $(...).
echo "Status: $(shui pill warning beta)"
echo "$(shui pill success stable) $(shui pill muted deprecated)"Status: beta
stable deprecatedshui pill <type> <text>
shui pill 135 "custom" # any 256-colour code (0β255)Available types: success error warning info primary muted accent or any 0β255 colour code
Bordered content block with an optional title. Inline components work inside content.
shui box "Simple content inside a box"
shui box --title="Summary" "3 installed\n1 skipped\n0 errors"
shui box --title="Status" "$(shui badge success OK) All systems nominal"ββββββββββββββββββββββββββββββββββββββββββββββ
β Simple content inside a box β
ββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββ Summary ββββββββββββββββββββ
β 3 installed β
β 1 skipped β
β 0 errors β
ββββββββββββββββββββββββββββββββββββββββββββββPipe-separated (|) rows by default. First argument is the header. Column widths adjust automatically. Use --sep to change the delimiter.
shui table \
"Package|Version|Status" \
"node|20.11.0|$(shui badge success OK)" \
"bun|1.1.3|$(shui badge success OK)" \
"python|3.12.0|$(shui badge warning outdated)"βββββββββββ¬βββββββββββ¬βββββββββββ
β Package β Version β Status β
βββββββββββΌβββββββββββΌβββββββββββ€
β node β 20.11.0 β OK β
β bun β 1.1.3 β OK β
β python β 3.12.0 β outdatedβ
βββββββββββ΄βββββββββββ΄βββββββββββAdds a newline by default. Use --inline for loop-based updates.
shui progress 50 100
shui progress 50 100 --width=30 --label="Downloading "
# loop use
for i in {1..100}; do
shui progress $i 100 --inline
sleep 0.05
done
echoββββββββββββββββββββββββββββββββββββββββ 50%
Downloading ββββββββββββββββββββββββββββββ 50%iTerm2 Dock/tab badge β pass --iterm (or --iterm=<state>) to also update the native macOS progress indicator. No-op in other terminals.
| Flag | iTerm2 state |
|---|---|
--iterm / --iterm=normal |
Normal (blue) |
--iterm=success |
Success (green) |
--iterm=warning |
Warning (yellow) |
--iterm=error |
Error (red) |
--iterm=indeterminate |
Spinning (no percentage) |
--iterm=clear |
Dismiss the indicator |
for i in $(seq 0 5 100); do
shui progress $i 100 --label="Downloading " --iterm --inline
sleep 0.05
done
shui progress 0 100 --iterm=clear --inline
echoRuns a command in the background with a spinner. Exits with the command's exit code.
In iTerm2, automatically emits an indeterminate badge while running, switches to success or error on completion, then clears.
shui spinner "Installingβ¦" -- brew install ripgrep
shui spinner \
--success="Installed!" \
--fail="Installation failed" \
"Installingβ¦" -- npm installβ Ή Installing brew packages...
β
Installed!Prompt the user for confirmation, a selection, or free-form input.
# Confirm β exits 0 for yes, 1 for no
shui confirm "Deploy to production?"
shui confirm --default=y "Continue?"
# Select β prints the chosen option to stdout
choice=$(shui select "Pick a profile:" work personal staging)
# Input β prints the entered value to stdout
name=$(shui input "Your name:")
name=$(shui input --default="world" "Your name:")βΉοΈ Deploy to production? [y/N]
βΉοΈ Pick a profile:
1) work
2) personal
3) staging
β
βΉοΈ Your name: (world)| Theme | Description |
|---|---|
default |
256-colour with automatic 16-colour fallback |
minimal |
Clean 16-colour ANSI palette |
plain |
No colour β text and ASCII icons only |
SHUI_THEME=minimal source shui.zshOr export it from your shell profile so all scripts pick it up automatically:
export SHUI_THEME=minimalshui respects the NO_COLOR convention. When $NO_COLOR is set, inline components (badge, pill) fall back to ASCII representations and no colour codes are emitted.
Generate a new theme pre-filled with all tokens:
shui theme create mytheme
# β src/themes/mytheme.zshA custom theme sources default.zsh first β only override the tokens you want to change:
# src/themes/mytheme.zsh
source "${SHUI_DIR}/src/themes/default.zsh"
SHUI_COLOR_PRIMARY=$(_shui_color "38;5;135" "0;35") # purple
SHUI_COLOR_ACCENT=$(_shui_color "38;5;135" "0;35")Load it:
SHUI_THEME=mytheme source shui.zshManage themes:
shui theme list # list available themes
shui theme validate # check all required tokens are defined| Token | Purpose |
|---|---|
SHUI_RESET |
Reset all styles |
SHUI_BOLD SHUI_DIM SHUI_ITALIC SHUI_UNDERLINE |
Text styles |
SHUI_COLOR_PRIMARY |
Primary accent colour |
SHUI_COLOR_SUCCESS |
Success colour |
SHUI_COLOR_WARNING |
Warning colour |
SHUI_COLOR_ERROR |
Error colour |
SHUI_COLOR_INFO |
Info colour |
SHUI_COLOR_MUTED |
Secondary / dim text |
SHUI_COLOR_ACCENT |
Highlight accent |
SHUI_BG_SUCCESS SHUI_BG_WARNING SHUI_BG_ERROR SHUI_BG_INFO SHUI_BG_PRIMARY SHUI_BG_MUTED |
Badge background colours |
SHUI_ICON_SUCCESS SHUI_ICON_ERROR SHUI_ICON_WARNING SHUI_ICON_INFO |
Status icons |
SHUI_ICON_BULLET SHUI_ICON_ARROW SHUI_ICON_CHECK SHUI_ICON_CROSS |
UI icons |
| Set | Requires | Description |
|---|---|---|
nerd |
Nerd Font | Rich glyphicons (default) |
emoji |
Nothing | Unicode emoji, works everywhere |
none |
Nothing | No icons β text only |
SHUI_ICONS=emoji source shui.zsh # emoji
SHUI_ICONS=nerd source shui.zsh # nerd font (default)
SHUI_ICONS=none source shui.zsh # no iconsCombine freely with any theme:
SHUI_THEME=minimal SHUI_ICONS=emoji source shui.zsh
SHUI_THEME=plain SHUI_ICONS=none source shui.zsh # fully plainzsh demo.zsh
zsh demo.zsh --interactive # includes confirm, select, and inputTasks are managed with mise:
mise run test # run all test suites
mise run lint # syntax-check all Zsh source files
mise run demo # run the visual component demoThe test suite lives in tests/ and uses a lightweight Zsh harness with inline β/β output per assertion.
mise run test
# or run a single suite directly
zsh tests/test-components.zsh
zsh tests/test-feat-close-api-gap.zshThe shared harness (tests/_harness.zsh) provides assert_eq, assert_contains, assert_not_contains, assert_exit_ok, and strip_ansi. Both suites source it.
mise run lint
# equivalent to:
zsh -n shui.zsh && zsh -n src/**/*.zsh- Zsh 5.0+
- A Nerd Font for the
defaultandminimalthemes β or useSHUI_ICONS=emojiorSHUI_ICONS=none
MIT