A well-designed command-line tool communicates progress to the user. When a task takes more than a second — whether it is fetching data from an API, building a project, or processing a file — an animated spinner transforms an opaque wait into a clear signal that work is happening. This guide compares the leading CLI spinner libraries across JavaScript, Python, and Rust, covering animation quality, composability with progress bars, and integration patterns.
Why Spinners Matter
Without visual feedback during long-running operations, users are left wondering if the tool has frozen. A spinner with descriptive text — “Installing dependencies…” followed by a green checkmark — turns anxiety into confidence. The best spinner libraries provide dozens of built-in animation styles, automatic terminal handling (hiding the cursor, clearing the line), and composability with progress bars for measurable tasks.
| Feature | ora (JS) | yaspin (Python) | indicatif (Rust) | halo (Python) |
|---|---|---|---|---|
| Language | JavaScript/TypeScript | Python | Rust | Python |
| GitHub Stars | 9,729 | 912 | 5,189 | 3,024 |
| Spinner Styles | 70+ (cli-spinners) | 60+ | 12+ built-in | 60+ |
| Progress Bars | Separate | Separate | Built-in (composable) | No |
| Custom Spinners | Yes | Yes | Yes | Yes |
| Non-TTY Safe | Yes | Yes | Yes | Yes |
| Async Support | Native Promise | Context manager | Sync | Context manager |
| Color Output | Via chalk | Via termcolor | Built-in | Via termcolor |
| Last Update | June 2026 | Feb 2026 | July 2026 | June 2024 |
ora: The JavaScript Spinner Standard
ora is the most popular terminal spinner library in the npm ecosystem, with nearly 10,000 stars and over 5 million weekly downloads. It provides 70+ built-in spinner animations from the cli-spinners package and a clean, chainable API.
| |
ora’s strengths include its vast animation library, built-in color support via chalk, and straightforward API. For progress bars, JavaScript developers typically pair ora with cli-progress or use listr2 (which wraps ora for multi-step task lists).
yaspin: Python’s Lightweight Spinner
yaspin (“Yet Another SPINner”) brings elegant terminal spinners to Python with a focus on simplicity and context-manager ergonomics. It supports 60+ spinner styles and integrates naturally with Python’s with statement pattern.
| |
yaspin’s ok() and fail() methods automatically stop the spinner and replace it with a success or failure symbol — a pattern that works well in scripts with clear start/finish boundaries. The Spinners enum provides access to all animations from the cli-spinners JSON specification.
indicatif: Rust’s Progress Powerhouse
indicatif takes a broader approach than pure spinner libraries — it provides both spinners and progress bars in a unified library with first-class composability. With over 5,000 GitHub stars, it is the standard for terminal progress in the Rust ecosystem, used by tools like cargo, rustup, and fd.
| |
indicatif’s MultiProgress allows multiple progress bars and spinners to render simultaneously without terminal flickering — a feature that JavaScript and Python spinner libraries typically require additional packages to achieve. The template-based style system, while powerful, requires more upfront configuration than ora or yaspin.
halo: Python’s Styled Alternative
halo brings a specific aesthetic to Python terminal spinners — it supports colored output, text placement around the spinner, and Jupyter notebook integration out of the box. With 3,024 GitHub stars, it predates yaspin and influenced the Python spinner ecosystem.
| |
halo’s main differentiator is Jupyter/IPython support — it renders spinners correctly in notebook cells, whereas yaspin focuses on terminal environments. However, halo has been less actively maintained (last release June 2024), and for new Python projects, yaspin or rich.console.Status are generally preferred.
Integration Patterns
Pairing with progress bars: In production CLIs, spinners signal ongoing work for indeterminate-duration tasks, while progress bars show completion percentage for measurable tasks. Rust’s indicatif handles both natively. In JavaScript, listr2 wraps ora for multi-step task lists. In Python, rich provides both Status (spinner) and Progress in a single library.
Non-TTY environments: All four libraries degrade gracefully in non-interactive terminals (CI pipelines, redirected output). They suppress animations and output plain text instead.
Error handling: ora, yaspin, and halo all provide .fail() methods that replace the spinner with a red X and error message. indicatif uses .finish_with_message() or .abandon_with_message().
For more terminal tooling comparisons, explore our Go CLI Libraries guide, our Terminal History Sync comparison, and our JSON Processing CLI Tools overview.
Composability with Task Runners and Observability
Modern CLI tools rarely use spinners in isolation — they are part of a broader output strategy that includes structured logging, progress bars, and task orchestration.
ora + listr2 (JavaScript): The listr2 library wraps ora spinners to create elegant multi-step task lists. Each step gets its own spinner with a title, and steps can run sequentially or in parallel. When a step completes, its spinner transforms into a checkmark. This pattern is used by tools like create-react-app and nx for project scaffolding.
| |
indicatif + tracing (Rust): indicatif integrates with the tracing ecosystem through tracing-indicatif. This allows structured log spans to automatically get progress bars or spinners — when a span is entered, indicatif renders a spinner; when it exits, the spinner is replaced with timing information. This bridges the gap between developer-oriented structured logs and user-facing terminal output.
yaspin + rich (Python): For Python tools that need both formatted output and spinners, rich provides Progress and Status (spinner) that work alongside its table, syntax highlighting, and layout features. However, if you only need a spinner, yaspin’s 60+ animation styles provide more variety than rich.status. Many projects use yaspin for the spinner and rich for everything else.
FAQ
When should I use a spinner vs. a progress bar?
Use a spinner when the duration is unknown (API calls, network operations) and a progress bar when you know the total work amount (file downloads, batch processing). Many libraries allow transitioning from spinner to progress bar when the total becomes known mid-operation.
Can I display multiple spinners simultaneously?
indicatif (Rust) supports this natively via MultiProgress. In JavaScript, listr2 provides multi-step task lists with individual spinners. In Python, rich offers Live displays for multiple concurrent renderables.
Which library works best in Docker/CI environments?
All four libraries detect non-TTY environments and disable animations automatically. indicatif and ora are particularly well-tested in CI pipelines. yaspin uses the TERM environment variable for detection and falls back to plain text.
How do I create custom spinner animations?
ora and yaspin accept custom frame arrays (arrays of strings that cycle frame by frame). You can define custom animations like ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"] for a moon phase spinner. indicatif uses tick_strings() for custom frame sequences.
What is the best overall choice for a new project?
For JavaScript/TypeScript: ora (9,729 stars, actively maintained, huge ecosystem). For Python: yaspin if you want just spinners, rich if you want spinners + progress bars + tables + syntax highlighting in one library. For Rust: indicatif is the clear winner with built-in composability and adoption by the Rust toolchain itself.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com