CLI tool
sindresorhus/ora avatar
sindresorhus/ora

ora: a Node spinner that takes over stdin and makes Ctrl+C wait

Elegant terminal spinner

9,771 stars295 forksJavaScriptMIT

At a glance

What is it?
ora animates a spinner and a line of text on a writable stream, with a small options object and a promise wrapper. Its default discardStdin puts the terminal in raw mode, which is what makes Ctrl+C wait for your blocking code, and that single default decides how the package behaves in scripts.
Who is it for?
Use ora for a CLI that owns the terminal while it works and can stay async throughout, which is the case the defaults assume. Do not adopt it without reading discardStdin if your command does long synchronous work, since a blocked event loop delays Ctrl+C until the work ends, and turn discardStdin off if you need terminal interrupts in a pipeline.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • sindresorhus/ora9,771 ★ · +19
Star history
Repository2026-09-222026-11-13
sindresorhus/ora9.8k9.8k

19 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

npm install ora is the whole install, and yocto-spinner is the named alternative

There is no binary to fetch and no global install step. The package is published to npm under the name ora and the install is a single line:

sh
npm install ora

If the dependency count is the thing that concerns you, the project points at yocto-spinner as a smaller alternative from the same author, so the size-sensitive decision is offered rather than left to you to research. What you get after installing is an ES module with one default export and a matching type declaration file, and the usage example is short:

js
import ora from 'ora';

const spinner = ora('Loading unicorns').start();

setTimeout(() => {
	spinner.color = 'yellow';
	spinner.text = 'Loading rainbows';
}, 1000);

Passing a string to ora is a shortcut for the text option, so the constructor and the options object are the same entry point.

The options object carries the real decisions, and four defaults are worth memorizing

ora accepts either a string or an options object, and every visible behavior comes from that object. text is the string beside the spinner. prefixText and suffixText each accept a string or a function returning a string, and an empty string removes them. color defaults to cyan and accepts black, red, green, yellow, blue, magenta, cyan, white, gray, or false to switch coloring off entirely. hideCursor defaults to true, so the cursor is hidden unless you say otherwise. indent defaults to 0 and takes a number of spaces. interval defaults to whatever the chosen spinner recommends, or 100 when the spinner has no recommendation, which is why you rarely set it. stream defaults to process.stderr, and switching it to process.stdout moves the animation off the error channel and into your normal output.

On Windows outside Windows Terminal, your chosen spinner is replaced by line

The spinner option takes the name of one of the provided spinners, and the default is dots. You can also pass your own object with a frames array and an optional interval, so a custom animation is three lines of data rather than a fork:

js
{
	frames: ['-', '+', '-'],
	interval: 80 // Optional
}

The override is not unconditional. On Windows, except in Windows Terminal, ora always uses the line spinner because the Windows command-line does not have proper Unicode support. A frame set built from box-drawing or braille characters will therefore look broken on a legacy console host while looking correct everywhere else, and nothing in the option tells you the substitution happened. The package detects this with an is-unicode-supported dependency, and the repository keeps example.js so you can try spinner definitions yourself, though example.js is not part of the published file list, which is limited to index.js and index.d.ts.

isEnabled is not isSilent, so a piped run still prints its text

Two similar options do different jobs and confusing them is an easy mistake. When isEnabled is not specified, the spinner is enabled if the stream is being run inside a TTY context, meaning it is not spawned or piped, and or if it is not in a CI environment. Set it explicitly to force the decision either way. Crucially, isEnabled set to false does not mean nothing is written: it means the spinner, the colors and the other ansi escape codes are suppressed, and the text is still logged. isSilent is the switch that stops output entirely, since it disables the spinner and all log text and makes isEnabled be considered false. So a CI job that wants readable logs with no animation wants isEnabled false, and a script that wants total quiet wants isSilent true.

discardStdin defaults to true, and raw mode is why Ctrl+C stops being immediate

discardStdin defaults to true and discards stdin input, except Ctrl+C, while the spinner runs if stdin is a TTY. The reason is cosmetic and real: it stops the spinner from twitching when someone types, stops broken lines when Enter is pressed, and prevents input from being buffered while the spinner runs. The cost is documented in detail. Putting stdin into raw mode means Ctrl+C no longer generates SIGINT from the terminal, and ora re-emits Ctrl+C from stdin input instead. If your code blocks the event loop with synchronous work, Ctrl+C handling is delayed until that blocking work ends. The stated remedies are to use async APIs, a worker thread or a child process, or to set discardStdin to false. On Windows the option has no effect, because there is no good way to implement discarding stdin properly there.

succeed, fail, warn and info all stop the spinner and persist the line underneath it

Ending a spinner is a family of calls that all do the same three things: stop the animation, replace it with a colored symbol, and persist the current text or whatever text you pass in. succeed uses a green check mark, fail a red cross, warn a yellow warning sign, and info a blue information sign. Each returns the instance, so they chain onto start. The generic form is stopAndPersist, which takes a symbol defaulting to a single space, a text defaulting to the current text, and prefixText and suffixText that persist alongside the symbol. stop clears the spinner entirely, while clear does the same on the display, and render forces a new frame by hand when you need a repaint that the interval has not triggered yet. frame returns the next frame without drawing it, and isSpinning plus interval are readable for code that reacts to state.

oraPromise maps fulfill and reject onto succeed and fail with computable text

The promise wrapper removes the bookkeeping when all you want is a spinner around an operation. oraPromise accepts either a promise or a function that receives the spinner and returns a promise, starts the spinner, stops it with succeed if the promise fulfills and with fail if it rejects, and returns the promise so it can be awaited:

js
import {oraPromise} from 'ora';

await oraPromise(somePromise);

The options argument takes everything the constructor takes, plus successText and failText. Both accept a string or a function, where the success function receives the resolved result and the fail function receives the thrown error, which is what lets you put a real result count into the final line. Leaving either as undefined keeps the text the spinner already had.

Node 20 is the floor, and the package publishes an ES module entry only

The runtime requirement is declared in package.json as Node 22 or later under engines, which is the first thing to check before adding the dependency. The package is type module, and its exports map publishes a types entry pointing at index.d.ts and a default entry pointing at index.js, with no separate CommonJS entry, so a require-based toolchain has to reach it another way. The files list contains only those two, meaning the examples and the test file stay in the repository rather than in your node_modules. Eight runtime dependencies carry the work: chalk and log-symbols for output, cli-cursor and stdin-discarder for terminal state, cli-spinners for the frame sets, is-interactive and is-unicode-supported for detection, and string-width for measuring. The test script runs xo, then the test file under the built-in node test runner with NODE_ENV=test.

Editorial conclusion

Use ora for a CLI that owns the terminal while it works and can stay async throughout, which is the case the defaults assume. Do not adopt it without reading discardStdin if your command does long synchronous work, since a blocked event loop delays Ctrl+C until the work ends, and turn discardStdin off if you need terminal interrupts in a pipeline. Before pinning, check that Node 22 or later is your floor, since that is what the package declares, and remember the last push was 2026-10-31 with v10.0.0 published the same day.

Frequently asked questions

How do I install ora in a Node project?

Install it with npm install ora. The package declares Node 22 or later under engines, publishes as an ES module with a default export from index.js, and ships only index.js and index.d.ts. A smaller alternative from the same author is yocto-spinner.

Why is my ora spinner invisible in CI or when output is piped?

When isEnabled is not specified, the spinner is enabled only if the stream runs inside a TTY context, meaning it is not spawned or piped, and or it is not in a CI environment. Setting isEnabled to false still logs text, it only suppresses the spinner, colors and other ansi escape codes, while isSilent suppresses all output.

How do I stop an ora spinner and keep the message on screen?

Call succeed, fail, warn or info, which stop the spinner, replace it with a colored symbol and persist the current text or the text you pass. stopAndPersist does the same with your own symbol, where the default is a single space, and a text that defaults to the current text.

Does ora get in the way of Ctrl+C while the spinner runs?

It can. discardStdin defaults to true and puts stdin into raw mode, where Ctrl+C no longer generates SIGINT from the terminal, and ora re-emits it from stdin input instead. Synchronous work that blocks the event loop delays the interrupt until it finishes, so use async APIs, a worker thread or a child process, or set discardStdin to false. The option has no effect on Windows.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sindresorhus/ora on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sindresorhus-ora.svg)](https://hysenlabs.com/projects/sindresorhus-ora)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/sindresorhus-ora.svg)](https://hysenlabs.com/projects/sindresorhus-ora)

Was this useful?