Skip to content

Show progress and spinners

output has three progress-feedback helpers, all obtained from a Renderer so they share its writer, theme and interactivity. Each animates in a terminal and prints plain lines under CI — the same code, no branching.

Point the renderer at os.Stderr for progress feedback so it does not pollute a piped stdout:

r := output.New(output.WithWriter(os.Stderr))

Spinner — indeterminate work

err := r.Spin(ctx, "Fetching release", func(ctx context.Context) error {
    return download(ctx)
})

Interactive: an animated spinner next to the message that clears on completion. Non-interactive: Fetching release... then Fetching release... done (or ... failed). A Ctrl-C while the spinner is active cancels fn's context and returns context.Canceled — never a false success.

Need a return value? Use the free generic function:

release, err := output.SpinWithResult(r, ctx, "Fetching", func(ctx context.Context) (Release, error) {
    return fetch(ctx)
})

Progress bar — known total

p := r.Progress(len(files), "Uploading")
for range files {
    upload()
    p.Increment() // or p.IncrementBy(n)
}
p.Done()

Interactive: a live bar Uploading [=====> ] 12/40 (30%). Non-interactive: a line at each 10% boundary, so logs are informative without flooding.

Status — multi-step operations

st := r.Status()
st.Update("Resolving dependencies")
st.Success("Dependencies resolved")
st.Update("Building")
st.Fail("Build failed")
st.Done()

Update replaces the current line in place (interactive) with a spinner-framed message; Success/Warn/Fail finalise the current step onto its own line with the themed icon; Done clears any pending in-place line. All three helpers are safe for concurrent use.

Change the icons, colours, spinner frames and bar runes via theming.