CLI tool
listr2/listr2 avatar
listr2/listr2

listr2: task lists for interactive command line interfaces

NodeJS Task List derived from the best! Create beautiful CLI interfaces via easy and logical to implement task lists that feel alive and interactive.

693 stars43 forksTypeScriptMIT

At a glance

What is it?
listr2 is a TypeScript library for drawing task lists in a terminal, with a set of renderers for different levels of detail, nested subtasks, retries, rollback, conditional enabling, and prompt adapters that let the same task ask a question through whichever prompting library the application already uses. It is maintained as a monorepo with the core package, a manager extension, and two prompt adapters.
Who is it for?
listr2 fits a Node developer whose command line tool has more than one step and wants the progress to look finished rather than printed line by line, and who already has a prompt library in the project. It does not fit a tool with a single action, since the machinery is for sequences, and it does not fit a team that cannot take on a package with its own renderer model and its own release train.
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 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

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

Editorial analysis

A library that owns the terminal while it works

listr2 is a Node task list library, and its own description is the pitch: create beautiful command line interfaces through task lists that are easy and logical to implement, and that feel alive and interactive.

The word derived is the key. listr2 started as a fork of the original listr, and the fork exists because the original had limitations the maintainer wanted removed. What survives from the original is the shape of the API, a list of tasks that you define with a title and a function, and a renderer that redraws the list as the work proceeds.

The difference in practice is the rendering. Instead of printing a line per step and leaving a scroll of history behind, a task list renderer keeps a live view of the tasks, updating in place, showing which are running, which are done, and which failed, and it can rewrite the display with the output of a task as it arrives. The demo in the repository is a recorded terminal session of the default renderer, and there are further recordings for the simple and the verbose renderers.

The project is MIT licensed, the last push was on 2026-10-20, and the release cadence is fast: two versions of the main package on the same day in October 2026, and a prompt adapter released alongside them.

The documentation is published separately from the repository, with the site carrying the guides and the source of the documentation itself living in the repository, so the docs are edited in the same place as the code.

The project is also funded through GitHub sponsors and an Open Collective page, which is a small thing to know when you are deciding whether to depend on a package that a single maintainer writes.

Four packages, and what each one is for

The repository is a monorepo, and the readme lists the packages it publishes.

The first is the core package, listr2, which is the one an application depends on. The second is an extension, the manager package, and the example directory has a file for it, which is a hint at what it does. The third and fourth are prompt adapters, one for the enquirer library and one for the inquirer library.

The prompt adapters are the design decision that tells you the most about the project. A task list that asks a question has to prompt somehow, and there are several popular prompt libraries in the Node ecosystem with incompatible APIs. Rather than picking one and forcing it on you, listr2 publishes an adapter per library, so a task that needs input can go through whichever prompt library the application already ships, or through the built in one if the application has none.

The names of the two adapters correspond to two libraries that a reader will already know, and the adapters are versioned and released independently of the core package, which is why the release list in the repository metadata shows an adapter version published on the same day as two core versions.

That arrangement is a deliberate answer to a common problem in this ecosystem. A library that hard codes a dependency on a prompting library forces its users to have two of them, and the two usually do not agree about behaviour, so the choice of adapter is a decision an application should make, not one the task list should make for it.

The manager package sits outside that pattern, since it is an extension rather than a swap, and the examples directory includes a file for it, which suggests it manages a set of tasks or a lifecycle of them rather than replacing the core.

Renderers, and the examples that show them

The examples directory is the best documentation of the library, because the file names are the features and each one is a small working program.

There are four renderer examples. The default renderer is the one in the demo recording, the simple renderer has its own recording, the verbose renderer has one as well, and there is a fallback condition example, which is the interesting one, because it shows the behaviour when the terminal cannot support what the renderer wants to draw. There is also a stderr example, which is the case where the output has to go somewhere other than standard output.

That set of files tells you the shape of the design. A renderer is a strategy for drawing the task list, and the library ships several because a terminal application has several contexts: a nice interactive terminal, a plain one, a pipeline, and a log file. A task list that only works in the first of those is a task list that breaks in continuous integration.

The fallback condition example is the one to look at first if you are evaluating the library, because it is the answer to the question of what happens when the fancy drawing is not possible.

The other example files cover the behaviour of the tasks themselves rather than the display: subtasks, a task that can be enabled or disabled, a task whose output is formatted, one that produces output, one that accesses its parent, one that retries, one that rolls back, one that prompts, and one that handles errors. The interruption example is the one that says something about how the library treats a signal, and the error handling example is the other half of that.

The examples are written in TypeScript, and the renderer examples ship a recording next to the source, which is the pattern of a library that tests what the user sees rather than only what the functions return.

Task behaviour, from subtasks to rollback

The example files name the task features, and it is a longer list than the name suggests.

Subtasks are the core capability, since a task list is rarely flat. A task can have children, and the renderer shows the hierarchy with the parent state reflecting the children.

Retries are supported as a task option, which matters for anything that talks to a network, because a task that fails once and aborts the whole list is a task list that will fail in production. Rollback is the other half of that idea, and it is the more interesting of the two, because a task that has already changed something and then fails can be given a way to undo it, which is what separates a list of steps from a workflow with a transaction.

Enabling and disabling a task conditionally is a third feature, and it is the one that separates a task list from a script. A task that is skipped because its condition was not met does not fail the list, and the renderer shows it as skipped rather than as failed, so the user can see what was decided rather than only what ran.

Output handling is the other large area, with two example files. One covers the format of a task's output, and the other covers the output itself. In a terminal application, what a task prints and how it appears next to the live task list are separate problems, and a library that lets you configure both is more useful than one that assumes a particular convention.

Access to the parent is what makes a hierarchical list programmable. A child task that can reach its parent can read the state the parent has accumulated, which is how a report is built from the results of a set of subtasks without a shared external object.

And there is the loader example, which is a separate file rather than a task example, so the loading of the list itself is a thing you can control, presumably for cases where the task definitions are not all known at the moment the list starts.

The monorepo, its scripts, and how it releases

The root package manifest describes the workspace, and it is a good map of the project's shape.

The package is private and marked as the monorepository root, it is an ES module package, and the author is one person with a personal domain as the homepage. The engine requirements are a recent node and a recent pnpm.

json
  "engines": {
    "node": ">=22.13.0",
    "pnpm": ">=10.0.0"
  },

The workspaces are the packages directory, the examples, and the docs, so a single install covers the libraries, the runnable examples, and the documentation site.

json
  "workspaces": [
    "packages/*",
    "examples",
    "docs"
  ],

The scripts are the ones you would expect from a workspace root, and they are written so that one command fans out to every package.

json
  "scripts": {
    "clean": "rimraf node_modules pnpm-lock.yaml packages/*/node_modules examples/node_modules docs/node_modules",
    "dev:start": "pnpm run --parallel -r dev:start",
    "build": "pnpm run -r build",
    "docs:build": "pnpm run --filter @listr2/docs docs:build",

The clean script is worth a second look, because it deletes the lock file as well as the installed modules, which is the only way to recover from a dependency tree that has gone wrong in a way the package manager cannot repair on its own. The documentation build is filtered to the docs workspace by name, and the continuous integration lint script runs with a workspace concurrency of one, which is the setting that keeps the linters of several workspaces from fighting over the same files.

The release side has its own configuration files. There is a multi package release configuration, a release configuration for the tool that publishes, and the dependency updates are handled by Renovate. The build itself uses tsdown, and the version of the bundler is pinned exactly rather than with a range, which is the kind of decision a project makes when the bundler is on the critical path of every release.

The toolchain, and the terminal snapshots

The development dependencies in the root manifest describe a modern TypeScript setup, and two entries in that list explain how the project is tested.

The test stack is jest at version 30, with the jest globals, the junit reporter, and a mock for the process object, and the tests are compiled with ts-jest and run against a node 22 tsconfig base. The compiler itself is TypeScript 6, the linter is ESLint 10 with the author's own shared configuration, and the formatter is Prettier with its own configuration file in the repository root.

The interesting one is the script at the top level named read-terminal-snapshots.sh, together with the renderer examples that ship a recording of their output. That combination is a snapshot test for what the user sees. A normal test asserts that a function returns a value, and a terminal snapshot test asserts that the drawn output matches a stored image or transcript, which is the only way to test a renderer honestly.

The switch to the compiled output is the SWC compiler, and the code is bundled with tsdown rather than with a general purpose bundler, which suits a library that ships a few entry points.

The rest of the repository is the usual set of configuration files: an editor configuration, a git attributes file, a gitignore, a GitLab pipeline file next to the GitHub directory, a pre-commit ignore and a prettier ignore, a ripgrep ignore, an editor settings directory, an ESLint flat configuration, a TypeScript configuration, a node version file, a mise configuration for tool versions, and a commitizen configuration pointing at the author's own commit message package.

The result is a repository where the development experience is itself maintained, which is the visible cost of a project that has been releasing several packages a month for years.

Editorial conclusion

listr2 fits a Node developer whose command line tool has more than one step and wants the progress to look finished rather than printed line by line, and who already has a prompt library in the project. It does not fit a tool with a single action, since the machinery is for sequences, and it does not fit a team that cannot take on a package with its own renderer model and its own release train. Verify first that the node and pnpm floors match your environment, since the monorepo asks for a recent node and a recent pnpm, that the renderer you want is the one you are importing, since the example files show several and they are not interchangeable, and that you pick the prompt adapter that matches the prompting library already in the project rather than adding a second one.

Frequently asked questions

What does listr2 do?

It is a Node library for drawing task lists in a terminal. You define tasks with a title and a function, and a renderer redraws the list as the work proceeds, showing which tasks are running, done, failed, or skipped, instead of printing a line per step.

Which packages does the listr2 monorepo publish?

The core listr2 package, the manager extension, and two prompt adapters, one for the enquirer library and one for the inquirer library. The adapters are versioned and released separately, so an application can prompt through the library it already uses instead of taking on a second one.

What task features does listr2 support?

The examples cover subtasks, conditional enabling, output and output formatting, access to the parent task, retries, rollback, prompting, error handling, and interruption. The rollback option is the one that turns a list of steps into a workflow that can undo a partial change.

What does the listr2 monorepo require to develop?

A recent node and a recent pnpm, with the workspaces covering the packages, the examples, and the docs. The root scripts fan out to every package, and the build uses tsdown, the tests use jest with ts-jest, and the linter and formatter are ESLint and Prettier.

How does listr2 test what the terminal shows?

With terminal snapshots. There is a script at the top of the repository for reading the stored snapshots, and the renderer examples ship a recording of their output next to the source, so the drawn result is compared rather than only the returned value.

Official sources

  1. Issues
  2. License: MIT
  3. listr2/listr2 on GitHub
  4. README
  5. Releases
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/listr2-listr2.svg)](https://hysenlabs.com/projects/listr2-listr2)

Was this useful?