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
────────────────────────────────────────────────────────────────────────────────Renders a section header, runs a command, then prints elapsed time. Returns the command's exit code.
shui screen "Building" -- npm run build
shui screen "Running tests" -- zsh tests/test-components.zshBuilding
✅ Build complete
⏱ Building · 3s
Running tests
...
⏱ Running tests · 1m 12sshui screen <title> -- <command> [args…]Lightweight per-step timing. Call timer-start before a block and timer-end <label> after — prints elapsed time in muted colour without affecting output or exit codes.
shui timer-start
mise plugins update
shui timer-end "Plugin index update"
shui timer-start
mise install
shui timer-end "Tool install"⏱ Plugin index update · 2s
⏱ Tool install · 8sshui timer-start
shui timer-end <label>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!Indeterminate loading indicator — loops for a fixed duration then clears the line. Use when you can't wrap a command in spinner.
shui loader "Installing packages"
shui loader --duration=10 "Building"
shui loader --style=dots "Connecting" # default
shui loader --style=pulse "Connecting"
shui loader --style=spinner "Connecting"Connecting...shui loader [--style=dots|pulse|spinner] [--duration=N] <msg>| Style | Description |
|---|---|
dots |
Cycling dot trail — . .. ... |
pulse |
Bold/dim alternating text |
spinner |
Braille spinner character |
One-shot text effects — play once and exit.
shui typewriter "Deploying to production…"
shui typewriter --delay=0.05 --color=success "Done!"
shui fade-in "Welcome"
shui fade-in --steps=10 "Welcome"shui typewriter [--delay=N] [--color=<type>] <text>
shui fade-in [--steps=N] <text>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 — numbered list, prints the chosen option to stdout
choice=$(shui select "Pick a profile:" work personal staging)
# Radio — ↑↓ to move, Enter to confirm. Prints chosen option to stdout.
env=$(shui radio "Target environment:" development staging production)
# Multiselect — ↑↓ to move, Space to toggle, Enter to confirm.
# Returns selected options newline-separated to stdout.
choices=$(shui multiselect "Which packages?" brew npm cargo)
selected=("${(@f)choices}")
# 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
→
ℹ️ Target environment:
○ development ← cursor highlight
● staging
○ production
ℹ️ Which packages? ↑↓ navigate · space toggle · enter confirm
■ brew ← selected + cursor
□ npm
□ cargo
ℹ️ 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 |
SHUI_ICON_ROBOT SHUI_ICON_APPLE SHUI_ICON_GIT SHUI_ICON_FOLDER SHUI_ICON_LINK SHUI_ICON_CLOUD |
Infrastructure icons |
SHUI_ICON_NODE SHUI_ICON_PYTHON SHUI_ICON_RUBY SHUI_ICON_RUST SHUI_ICON_GO SHUI_ICON_GEM SHUI_ICON_BREW |
Language & tool icons |
| Set | Requires | Description |
|---|---|---|
nerd |
Nerd Font | Rich glyphicons (default) |
emoji |
Nothing | Unicode emoji, works everywhere |
none |
Nothing | No icons — text only |
unicode |
Nothing | Base layer — always loaded automatically |
unicode.zshis sourced before the selected icon set on every load. It defines geometric symbols (›,●,▲, etc.) that work in any terminal. The selected icon set can override them —noneblanks them all,emojiinherits them unchanged.
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