1---
2name: charm-gum
3description: "Interactive shell script prompts, fuzzy filters, spinners, and styled output with gum. Use when building bash/shell script UIs, gum commands, interactive shell prompts, or CLI script workflows. NOT for Go terminal forms (use huh)."
4---
5
6# charm-gum
7
8gum is a CLI for glamorous shell scripts. All interaction writes to stdout; capture with `$()`. All commands render to stderr so stdout stays clean for piping.
9
10## Quick Start
11
12```bash
13brew install gum # macOS/Linux
14go install github.com/charmbracelet/gum@latest
15
16NAME=$(gum input --placeholder "your name")
17gum confirm "Continue?" && echo "hello $NAME"
18```
19
20Every flag has an env var equivalent: `--placeholder` = `GUM_INPUT_PLACEHOLDER`. Export env vars to set defaults project-wide.
21
22## Command Reference
23
24### input - single-line prompt
25
26```bash
27gum input [flags]
28# key flags:
29# --placeholder "text" hint text
30# --value "text" pre-filled value
31# --password mask input
32# --header "text" label above input
33# --width N fixed width (0 = terminal width)
34# --char-limit N max chars (default 400, 0 = unlimited)
35# --timeout 30s auto-submit after duration
36
37NAME=$(gum input --placeholder "full name" --header "Enter your name")
38PASS=$(gum input --password --placeholder "password")
39```
40
41### write - multi-line textarea
42
43```bash
44gum write [flags]
45# key flags:
46# --placeholder "text"
47# --header "text"
48# --width N, --height N
49# --show-line-numbers
50# --show-cursor-line
51# --max-lines N
52# ctrl+d to submit, ctrl+c to cancel
53
54BODY=$(gum write --placeholder "PR description..." --header "Description" --width 80)
55```
56
57### choose - pick from a list
58
59```bash
60gum choose [options...] [flags]
61# pipe options or pass as args
62# key flags:
63# --limit N max selectable (default 1)
64# --no-limit unlimited selection
65# --header "text"
66# --height N visible rows (default 10)
67# --cursor "> " cursor prefix
68# --selected "val" pre-selected item
69# --ordered preserve selection order
70# --timeout 30s
71
72TYPE=$(gum choose "fix" "feat" "docs" "chore" "refactor")
73PKGS=$(brew list | gum choose --no-limit --header "Remove packages")
74```
75
76### filter - fuzzy search a list
77
78```bash
79gum filter [options...] [flags]
80# reads from stdin or args; fuzzy match by default
81# key flags:
82# --limit N
83# --no-limit
84# --placeholder "text"
85# --header "text"
86# --height N
87# --value "text" initial filter query
88# --no-fuzzy prefix match only
89# --no-strict return query if no match
90# --reverse render from bottom
91
92SESSION=$(tmux list-sessions -F '#S' | gum filter --placeholder "pick session...")
93BRANCH=$(git branch | cut -c 3- | gum filter --placeholder "checkout...")
94```
95
96### confirm - yes/no prompt
97
98```bash
99gum confirm [prompt] [flags]
100# exits 0 = yes, 1 = no; use with && / ||
101# key flags:
102# --affirmative "Yes" confirm button label
103# --negative "No" cancel button label
104# --default which is pre-selected (true = yes)
105# --timeout 30s
106# --show-output echo chosen action to stdout
107
108gum confirm "Delete branch?" && git branch -D "$BRANCH"
109gum confirm "Overwrite?" --affirmative "Overwrite" --negative "Skip" || exit 0
110```
111
112### spin - spinner while command runs
113
114```bash
115gum spin [flags] -- <command>
116# key flags:
117# --title "text" message shown next to spinner
118# --spinner dot type: line,dot,minidot,jump,pulse,points,globe,moon,monkey,meter,hamburger
119# --show-output stream stdout/stderr live
120# --show-error show output only on failure
121# --timeout 60s
122
123gum spin --title "Installing deps..." -- npm install
124OUTPUT=$(gum spin --show-output --title "Fetching..." -- curl -s https://api.example.com/data)
125```
126
127### style - styled text output
128
129```bash
130gum style [flags] "text" ["text2" ...]
131# multiple strings are rendered as separate lines in one block
132# key flags:
133# --foreground "#hex"|"256color"
134# --background "#hex"|"256color"
135# --border none|hidden|normal|rounded|thick|double
136# --border-foreground
137# --align left|center|right
138# --width N, --height N
139# --margin "T R B L" (css shorthand)
140# --padding "T R B L"
141# --bold, --italic, --underline, --strikethrough, --faint
142
143gum style --foreground 212 --border rounded --padding "1 2" "Done!"
144```
145
146### format - render markdown, code, templates, emoji
147
148```bash
149gum format [flags] [text...]
150# key flags:
151# -t markdown|template|code|emoji (default: markdown)
152# -l python language hint for code type
153# --theme pink glamour theme for markdown
154
155echo "# Hello\n- item 1\n- item 2" | gum format
156cat script.sh | gum format -t code -l bash
157echo '{{ Bold "OK" }} {{ Color "99" "0" " gum " }}' | gum format -t template
158echo "I :heart: gum :candy:" | gum format -t emoji
159```
160
161### join - compose styled blocks side by side or stacked
162
163```bash
164gum join [flags] "block1" "block2"
165# flags:
166# --vertical stack top to bottom (default is horizontal)
167# --align left|center|right
168
169# always quote gum style output to preserve newlines
170A=$(gum style --border rounded --padding "0 2" "left")
171B=$(gum style --border rounded --padding "0 2" "right")
172gum join "$A" "$B"
173```
174
175### file - file picker from tree
176
177```bash
178gum file [path] # defaults to current dir
179# flags: --cursor, --height, --show-hidden
180
181$EDITOR "$(gum file $HOME)"
182```
183
184### pager - scrollable viewer
185
186```bash
187gum pager < README.md
188gum pager --show-line-numbers < file.txt
189```
190
191### table - pick a row from CSV
192
193```bash
194gum table < data.csv
195gum table -c Name,Age,Role < users.csv | cut -d',' -f1
196```
197
198### log - structured log output
199
200```bash
201# levels: debug, info, warn, error, fatal
202gum log --level info "Server started" port 8080
203gum log --structured --level error "Failed" file foo.txt
204gum log --time rfc822 --level warn "Slow response"
205```
206
207## Shell Script Patterns
208
209### 1. conventional commit helper
210
211```bash
212#!/bin/bash
213set -e
214
215TYPE=$(gum choose "fix" "feat" "docs" "style" "refactor" "test" "chore")
216SCOPE=$(gum input --placeholder "scope (optional)")
217[ -n "$SCOPE" ] && SCOPE="($SCOPE)"
218
219SUMMARY=$(gum input --value "$TYPE$SCOPE: " --placeholder "short summary")
220BODY=$(gum write --placeholder "longer description (ctrl+d to skip)" --height 6)
221
222gum confirm "Commit?" || exit 0
223git commit -m "$SUMMARY" ${BODY:+-m "$BODY"}
224```
225
226### 2. interactive branch cleanup
227
228```bash
229#!/bin/bash
230set -e
231
232# pick branches to delete
233BRANCHES=$(git branch | cut -c 3- | gum choose --no-limit --header "Branches to delete")
234[ -z "$BRANCHES" ] && exit 0
235
236gum style --foreground 196 --bold "Will delete:"
237echo "$BRANCHES" | gum format
238
239gum confirm "Delete these branches?" --affirmative "Delete" --negative "Cancel" || exit 0
240
241echo "$BRANCHES" | while IFS= read -r branch; do
242 gum spin --title "Deleting $branch..." -- git branch -D "$branch"
243 gum log --level info "Deleted" branch "$branch"
244done
245```
246
247### 3. deploy script with env selection
248
249```bash
250#!/bin/bash
251set -e
252
253ENV=$(gum choose "staging" "production" --header "Deploy target")
254TAG=$(git tag --sort=-v:refname | gum filter --placeholder "pick version tag...")
255
256gum style \
257 --border rounded --border-foreground 214 \
258 --padding "1 3" --margin "1" \
259 "Deploy $TAG to $ENV?"
260
261gum confirm "Proceed?" --default=false || exit 0
262
263gum spin --title "Deploying $TAG to $ENV..." --show-error -- \
264 ./deploy.sh "$ENV" "$TAG"
265
266gum style --foreground 82 --bold "Deployed $TAG to $ENV"
267```
268
269### 4. quick file notes launcher
270
271```bash
272#!/bin/bash
273# browse vault notes, open selected in editor
274VAULT="$HOME/notes"
275
276FILE=$(find "$VAULT" -name "*.md" | sed "s|$VAULT/||" \
277 | gum filter --placeholder "search notes..." --height 20)
278
279[ -z "$FILE" ] && exit 0
280$EDITOR "$VAULT/$FILE"
281```
282
283### 5. package manager TUI
284
285```bash
286#!/bin/bash
287set -e
288
289ACTION=$(gum choose "install" "remove" "update" --header "Package action")
290
291case "$ACTION" in
292 install)
293 PKG=$(gum input --placeholder "package name")
294 gum spin --title "Installing $PKG..." -- brew install "$PKG"
295 ;;
296 remove)
297 PKGS=$(brew list | gum choose --no-limit --header "Select packages to remove")
298 [ -z "$PKGS" ] && exit 0
299 gum confirm "Remove selected?" || exit 0
300 echo "$PKGS" | xargs gum spin --title "Removing..." -- brew uninstall
301 ;;
302 update)
303 gum spin --title "Updating Homebrew..." --show-error -- brew update
304 gum spin --title "Upgrading packages..." --show-output -- brew upgrade
305 ;;
306esac
307
308gum log --level info "Done" action "$ACTION"
309```
310
311## Styling
312
313Colors accept ANSI 256 codes (`212`) or hex (`#FF79C6`). Padding/margin use CSS shorthand: `"1 2"` = top/bottom 1, left/right 2.
314
315```bash
316# banner helper pattern
317banner() {
318 gum style \
319 --foreground 212 --border-foreground 212 \
320 --border double --align center \
321 --padding "1 4" --margin "1 2" \
322 "$@"
323}
324banner "Build Complete" "v1.4.2"
325
326# side by side layout
327LEFT=$(gum style --border rounded --padding "0 3" --foreground 82 "OK")
328RIGHT=$(gum style --border rounded --padding "0 3" --foreground 196 "ERR")
329gum join "$LEFT" "$RIGHT"
330```
331
332Style env vars use `GUM_<CMD>_<FLAG>` format. Set in shell profile for persistent defaults:
333
334```bash
335export GUM_CHOOSE_CURSOR_FOREGROUND="#FF79C6"
336export GUM_INPUT_PLACEHOLDER="..."
337export GUM_SPIN_SPINNER="dot"
338```
339
340## Common Mistakes
341
342- **capturing output**: gum writes the prompt to stderr, result to stdout. `VAL=$(gum input)` works correctly.
343- **spin command separator**: `--` is required before the command: `gum spin --title "..." -- npm install`. Without it gum parses your command as its own flags.
344- **confirm exit code**: `gum confirm` returns 0 for yes, 1 for no. Use `&&`/`||` not `if [ $? -eq 0 ]` - both work but `&&` is idiomatic.
345- **join with newlines**: always quote `$(gum style ...)` in join args or newlines collapse: `gum join "$A" "$B"` not `gum join $A $B`.
346- **filter with no match**: by default `--strict` is on - filter returns nothing if no match. Use `--no-strict` to return the query string instead.
347- **choose vs filter**: `choose` = static list, cursor navigation. `filter` = fuzzy search while typing. Use filter for long lists.
348- **multi-select output**: each selection on its own line. Iterate with `while IFS= read -r item; do ... done <<< "$SELECTION"`.
349
350## Checklist
351
352- [ ] capture interactive output with `$()`, not redirect
353- [ ] add `-- command` separator for `gum spin`
354- [ ] handle empty selection (`[ -z "$VAR" ] && exit 0`)
355- [ ] use `--no-limit` for multi-select, iterate output line by line
356- [ ] use `--default=false` on destructive confirms
357- [ ] quote `$(gum style ...)` when passing to `gum join`
358- [ ] set `--timeout` for unattended or CI-adjacent scripts
359- [ ] test ctrl+c behavior - gum exits non-zero, handle with `set -e` or explicit checks