Splash screens and loaders for command line tools

Give your CLI a startup splash or an animated loader in ASCII. Copy the piece source into your Node project, or print frames from any language.

Loading the builder…

/WHY

A command line tool is judged in its first second. A short splash on start, or a moving loader while it works, makes it feel finished and tells people it has not frozen.

The builder below bakes the real frames of a piece into a single file in Node.js, Python, Go, Rust or Bash, with no dependencies. It draws in place, hides the cursor while it plays, and prints nothing when the output is piped or running in CI, so logs stay clean.

/EXAMPLES

Ready-made examples to copy

Each one is complete. Copy it as it is, or change the piece name to any piece in the library.

Node: wrap any async task in a spinner

The pattern most CLIs need: play while a promise runs, then print a tick.

const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

export async function withSpinner(label, task) {
  if (!process.stdout.isTTY) return task;
  let i = 0;
  const timer = setInterval(() => process.stdout.write(`\r${FRAMES[i++ % FRAMES.length]} ${label}`), 80);
  try { return await task; }
  finally { clearInterval(timer); process.stdout.write(`\r✓ ${label}\n`); }
}

// await withSpinner("Installing", install());

Python: a context manager

Use it as with spinner("Building"): around any block.

import itertools, sys, threading, time
from contextlib import contextmanager

@contextmanager
def spinner(label, frames="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"):
    if not sys.stdout.isatty():
        yield; return
    stop = threading.Event()
    def run():
        for f in itertools.cycle(frames):
            if stop.is_set(): break
            sys.stdout.write(f"\r{f} {label}"); sys.stdout.flush(); time.sleep(0.08)
    t = threading.Thread(target=run); t.start()
    try: yield
    finally:
        stop.set(); t.join(); print(f"\r✓ {label}")

with spinner("Building"):
    time.sleep(2)

Go: a splash before main work

Frames as a string slice; redraw in place by moving the cursor up.

var frames = []string{"[=   ]", "[ =  ]", "[  = ]", "[   =]", "[  = ]", "[ =  ]"}

func spin(done <-chan struct{}) {
	for i := 0; ; i++ {
		select {
		case <-done:
			fmt.Print("\r[done]\n")
			return
		default:
			fmt.Printf("\r%s", frames[i%len(frames)])
			time.Sleep(100 * time.Millisecond)
		}
	}
}

Show a still banner in --help

Print one frame above your usage text. Copy any frame as text from a piece page.

const BANNER = String.raw`
  ┌─┐┌─┐┌─┐┬┬
  ├─┤└─┐│  ││
  ┴ ┴└─┘└─┘┴┴
`;

if (process.argv.includes("--help")) {
  console.log(BANNER);
  console.log("Usage: mytool <command> [options]");
}

Respect NO_COLOR and CI

The checks every terminal animation should make before drawing.

const animate =
  process.stdout.isTTY &&            // a real terminal, not a pipe
  !process.env.CI &&                 // not in CI logs
  process.env.TERM !== "dumb" &&     // not an emacs shell or similar
  !process.argv.includes("--quiet");
const color = animate && !("NO_COLOR" in process.env);

How to do it

  1. Pick a loader or a short animation from the Interface or Type categories.
  2. Copy the piece source from its page into your project.
  3. Call the draw function on an interval and write each frame, moving the cursor home first.
  4. Stop the timer when your real work finishes.

/GUIDE

How the in-place animation works

Each frame is printed, then the cursor is moved back up by the number of lines in the frame (the escape sequence ESC [ n A) and the next frame is printed over it. The cursor is hidden with ESC [ ? 25 l while it plays and shown again afterwards, even if the program is stopped.

Single-line spinners only need a carriage return (\r) to go back to the start of the line.

Keep startup fast

A splash should never make a tool slower. Play it while real work happens, such as loading config or opening a connection, or cap it at a second. For tools people run many times a minute, show it only on the first run or behind a --splash flag.

Pipes, CI and accessibility

Always check that output is a terminal before animating. In CI logs, every frame becomes a new line and turns a log into thousands of lines. The baked code does this check for you.

Screen reader users hear every redraw. Offer a --quiet flag, and print a plain final line such as "✓ Built in 1.2 s" so the result is clear without the animation.

File size

Baked frames are plain text, usually 1 to 4 KB each. Sixteen frames of a 48 by 24 piece is about 20 KB, small enough to ship inside any binary. Use fewer frames or a smaller piece for tiny tools.

Pieces that work well for this

Open any of them to copy the code or export.

/FAQS

Splash screens and loaders for command line tools: frequently asked questions

Q. How do I add a terminal animation to my command line tool?
Pick a piece in the builder, choose your language, and download one code file with the frames baked in. It draws each frame in place, hides the cursor while it plays and prints your message afterwards. It uses only the standard library, so your CLI gains a terminal animation with no new dependencies.
Q. How do I make an ASCII progress bar for a CLI?
Choose the Progress Loader piece, set Use it as to Spinner while working, and put your real task where the code says. In the Node.js and Python versions the bar loops until the task finishes. It is an animation rather than a measurement, so for an exact percentage, draw your own bar from your progress value.
Q. How do I show a CLI spinner while a task runs?
Set Use it as to Spinner while working and pick a small piece such as Spinner Set. The Node.js version plays until a promise settles, and the Python version runs on a thread until your block of work is done, then prints a check mark with your message. Both draw nothing when the output is not a terminal.
Q. Which languages can the CLI animation builder generate?
It generates Node.js, Python, Go, Rust and Bash, each as a single file with the frames inside. For any other language, the Frames as JSON tab gives you the raw frames to load yourself. There is also a shorter Node version that imports the piece from the ascii-magic-art package instead of baking frames in.
Q. Do I need to install a package to add a splash screen?
No. The downloaded file contains the frames and uses only the standard library of its language, so you can drop it into any project. The optional package version is a smaller file because it imports the piece, but it adds a dependency. Pick whichever suits how you ship your tool.
Q. How does a terminal animation redraw in place without scrolling?
After each frame, the code moves the cursor back up by the number of lines in the frame with the ESC [ n A escape sequence, then prints the next frame over it. The cursor is hidden with ESC [ ? 25 l while it plays and shown again afterwards. One-line spinners only need a carriage return.
Q. Why does my CI log fill up with animation frames?
Something is animating without checking for a terminal, so every frame becomes a new log line. The builder's code checks whether standard output is a TTY and returns straight away when it is piped or running in CI. Add the same check to any spinner you write by hand.
Q. Can a CLI splash screen show colors?
The baked files print plain text, so they look the same in every terminal. Color comes from the ascii-magic-art command: npx --yes https://www.ascii-magic.com/ascii/cli.tgz plus a piece name plays it in 24-bit color where the terminal supports it, and switches color off when NO_COLOR is set or the output is piped.
Q. How many frames should I bake in, and how big will the file be?
The builder bakes 4 to 48 frames, with 16 as the default, and shows the size of the result next to the code. Each frame is plain text, usually 1 to 4 KB, so 16 frames of a mid-sized piece is roughly 20 KB. Use fewer frames or a smaller piece for tiny tools.
Q. Will an ASCII splash screen slow down my CLI?
Only if it plays on its own, so keep it short or run it during real work. The builder lets you set 0.5 to 6 seconds; for tools people run many times a minute, show it only on first run or behind a --splash flag. The Node.js and Python spinner versions last exactly as long as the task.
Q. Does the CLI animation work on Windows?
Yes, in Windows Terminal, PowerShell 7 and the terminal inside most code editors, which all understand the escape codes. The older cmd.exe console may print the codes as text instead of moving the cursor. Test it once in the terminal your users run most.
Q. How do I keep a CLI animation accessible?
Offer a --quiet flag that skips the animation, and always print a plain final line such as Built in 1.2 s so the result is clear without it. Screen readers can announce every redraw, which is noisy. The generated code already stays silent when the output is piped, which helps people who log or parse it.

Other ways to use it

Each one has its own builder.