Use when recording terminal demos, creating GIFs of CLI apps, writing VHS tape files, or generating demo recordings for TUI applications. Use when asked to "record a demo", "create a GIF of my CLI", "write a VHS tape", "make a terminal recording", "generate a demo for my TUI", or "set up VHS for CI".
Charmbracelet VHS renders a .tape script to GIF / MP4 / WebM / ASCII. Write the demo as code rather than capturing a screen, because a .tape re-renders unattended when the app changes and runs the same way in CI. For a throwaway recording nobody will re-run, asciinema is lighter.
# macOS
brew install charmbracelet/tap/vhs ffmpeg ttyd
# Debian/Ubuntu
sudo apt install ffmpeg && sudo snap install ttyd --classic && go install github.com/charmbracelet/vhs@latest
vhs --version && ffmpeg -version && ttyd --version # all three are required
Every Set goes above the first interaction command. A Set placed lower is silently discarded — see Gotchas.
Output demo.gif # 1. output (gif/mp4/webm/ascii)
Require my-app # 2. fail fast if the app is missing
Set Shell "bash" # 3. settings, all grouped before any command
Set FontSize 20
Set Width 1200
Set Height 600
Set Theme "Catppuccin Frappe"
Set WindowBar Colorful
Set Padding 20
Set TypingSpeed 75ms
Hide # 4. hidden setup — Ctrl+L before Show
Type "export TERM=xterm-256color"
Enter
Sleep 500ms
Ctrl+L
Show
Type "my-app --demo" # 5. visible interactions, with deliberate pauses
Sleep 500ms
Enter
Sleep 2s
Down 3 # 6. app interaction
Enter
Sleep 3s # generous final frame before the loop restarts
| Command | Purpose |
|---------|---------|
| Output file.gif | Output file + format (gif/mp4/webm/ascii); repeat for several formats |
| Require app | Fail if the program is not on PATH |
| Set Key Value | Terminal settings (FontSize, Width, Height, Theme, Framerate, …) |
| Type "text" / Type@100ms "text" | Emulate typing (optionally at a per-line speed) |
| Enter / Tab / Space / Escape | Key presses |
| Up/Down/Left/Right (Down 3) | Navigation with optional repeat count |
| Ctrl+key, Alt+key, Shift+key | Modifier combos |
| Sleep 2s | Fixed pause (ms or s) |
| Wait+Screen@30s /regex/ | Block until screen content matches, with a timeout |
| Hide / Show | Control recording visibility (execution continues either way) |
| Screenshot file.png | Capture the current frame |
| Env VAR "val" / Source other.tape | Set an env var / include another tape |
vhs validate demo.tape # parse-check, no rendering
vhs demo.tape # record
vhs themes # list themes
vhs new demo.tape # scaffold from template
Full command + settings detail: reference/COMMAND-REFERENCE.md.
| Use case | Width | Height | FontSize | |----------|-------|--------|----------| | README GIF | 1200 | 600 | 20 | | Docs/tutorial | 1400 | 800 | 18 | | Social media | 1200 | 630 | 22 | | Full TUI app | 1600 | 900 | 16 | | Compact CLI | 800 | 400 | 20 |
Read the app's --help output or its source to learn how it launches, which keys it responds to, and which states are worth showing. Draft a tape covering those states, then vhs validate demo.tape → vhs demo.tape → watch the rendered file → adjust. Watching is the step that matters: validation parses syntax and cannot tell you the demo outruns the viewer.
Bound the demo before drafting: a README GIF shows one workflow in 10–30 seconds — three to five states, each held long enough to read (Sleep 2s–3s). Past 30 seconds, cut states or split into a second tape rather than adding scenes; a demo that shows everything shows nothing, and the GIF size grows with it.
Copy-paste starting points (Basic CLI, Interactive TUI, Build-and-Run, Multi-Panel, CI golden file, composable Source tapes): reference/TEMPLATES.md.
✅ Output first, settings grouped above the commands, Require guarding the binary, a pause after typing and a long final frame:
Output demo.gif
Require my-app
Set Shell "bash"
Set FontSize 20
Set Width 1200
Set Height 600
Type "my-app status"
Sleep 500ms
Enter
Sleep 3s
❌ Same tape, three defects — Set Width lands after Type and is dropped, nothing separates typing from execution, and a missing binary produces a confusing empty GIF instead of an error:
Output demo.gif
Type "my-app status"
Enter
Set Width 1200
✅ Pacing a TUI whose startup time varies — wait on a screen match instead of guessing a Sleep:
Type "my-tui"
Enter
Wait+Screen@30s /Ready/
Down 3
Sleep 500ms
Enter
Sleep 3s
Set below the first command is silently dropped, and vhs validate still exits 0. Verified on vhs 0.11.0: Set FontSize 40 after a Type rendered at the default size, and the same holds for Set TypingSpeed — there is no per-setting exception. Keep every Set above the first Type/key command and confirm by watching the output, because nothing in the toolchain reports this. To vary typing speed mid-tape, use the per-line Type@100ms "…" form.vhs validate only parses. It never launches the app, so a missing binary, a wrong keybinding, or a demo that races ahead of the app all pass. Add Require <app> so a missing dependency fails loudly rather than rendering an empty terminal.Type "cmd" does not press Enter. Follow it with an explicit Enter, and put Sleep 500ms between them so the viewer can read the command before it runs.Hide hides output, not execution. Commands inside a Hide block still run. End the block with Ctrl+L before Show so leftover setup output doesn't open the recording.Wait times out. Raise it per command (Wait+Screen@30s /ready/) or globally (Set WaitTimeout 30s) for a slow-booting TUI, rather than padding with a longer Sleep that will still be wrong on a slower machine.Set Shell "bash" to get the same prompt on your machine and in CI.Hide block: Env TERM "xterm-256color" plus Type "stty rows 50 cols 120".Set Framerate 15 first — it is the cheapest win and costs little perceived smoothness — then gifsicle -O3 --lossy=80, and switch to MP4 before shrinking dimensions past readability.Size reduction, playback tuning, and the GIF-vs-MP4-vs-WebM decision tree: reference/OPTIMIZATION.md. Recording in GitHub Actions, plus ASCII output as a golden file for UI regression tests: reference/CI-INTEGRATION.md.
Pairs with build-tui — record the TUI right after building it, and commit the .tape next to the source so the GIF regenerates with the app. Use screenshot-local instead when the target is a web UI on localhost.
External: VHS · VHS Action · examples · themes · gifsicle.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer
Tags:vhs, recording, tui, demo, gif, terminal, cli, charmbracelet