Listr2 - Interactive Terminal Task Lists
Listr2 is a robust and highly interactive task list manager for Node.js command-line applications, designed to provide live updates and a dynamic user experience. As of version 10.2.1, it emphasizes modern JavaScript practices and is primarily used with TypeScript, shipping with comprehensive type definitions. It evolved from the original Listr package, offering a rewritten architecture for improved flexibility, extensibility, and performance, including advanced error handling, concurrent task execution, and support for multiple renderers (e.g., default, verbose, silent). Its release cadence is active, with frequent updates addressing bug fixes, dependency updates, and new features, often released in minor and patch versions. Key differentiators include its ability to manage complex multi-level tasks with live output, integration with popular prompt libraries like Inquirer.js and Enquirer through dedicated adapters, and a flexible API for customizability, making it suitable for complex CLI workflows.
Common errors
-
ERR_REQUIRE_ESM: require() of ES Module ... listr2.js from ... is not supported.
cause Attempting to import Listr2 using CommonJS `require()` syntax in an environment where it's treated as an ES Module.fixSwitch to ES Module import syntax: `import { Listr } from 'listr2';`. Ensure your `package.json` has `"type": "module"` if your entire project is ESM, or use a bundler if mixing CJS and ESM. -
TypeError: Listr is not a constructor
cause This usually happens when trying to `new Listr()` after a CJS `require()` that incorrectly resolves the ESM export, or if an incorrect symbol is imported.fixVerify that you are using `import { Listr } from 'listr2';` and that your environment supports ES Modules for this package. Avoid `const Listr = require('listr2').Listr;` as it might not work reliably. -
ListrError: A task has failed and `exitOnError` is enabled, exiting.
cause One of the tasks or subtasks threw an unhandled error, and the `exitOnError` option (defaulting to `true`) caused Listr2 to stop execution.fixWrap your task logic in `try...catch` blocks to gracefully handle errors, or set `exitOnError: false` in the Listr options to allow other tasks to continue running even after a failure.
Warnings
- breaking Listr2 requires Node.js version 22.13.0 or higher. Older Node.js versions are not supported and will result in errors.
- breaking Listr2 is a complete rewrite of the original `listr` package. It introduces a new API, different configuration options, and a more opinionated structure. Direct migration from `listr` without code changes is not possible.
- gotcha Listr2 is primarily designed for ES Modules (ESM). While tools may transpile it for CommonJS (CJS) compatibility, direct `require()` calls may lead to unexpected behavior or `TypeError: Listr is not a constructor`.
- gotcha When using prompt adapters (e.g., `@listr2/prompt-adapter-inquirer`), ensure the specific adapter package is installed alongside `listr2`. Failure to do so will result in runtime errors when a prompt task is encountered.
- deprecated Internal color styling utility `colorette` has been replaced with Node.js's native `util.styleText` for internal styling. While this is primarily an internal change, users who were relying on or overriding `colorette`'s behavior within Listr2 might notice differences.
Install
-
npm install listr2 -
yarn add listr2 -
pnpm add listr2
Imports
- Listr
const Listr = require('listr2')import { Listr } from 'listr2' - ListrDefaultRenderer
import { ListrDefaultRenderer } from 'listr2' - ListrTask
import type { ListrTask } from 'listr2'
Quickstart
import { Listr, ListrDefaultRenderer } from 'listr2';
interface MyContext {
input: string;
output: string;
}
async function runTasks() {
const tasks = new Listr<MyContext>(
[
{
title: 'Main task 1: Processing input',
task: async (ctx, task) => {
ctx.input = 'initial data';
task.output = `Input set to: ${ctx.input}`;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
},
{
title: 'Main task 2: With subtasks',
task: (ctx, task) =>
task.newListr(
[
{
title: 'Subtask 2.1: Fetching data',
task: async (_, subtask) => {
subtask.output = 'Fetching from external API...';
await new Promise((resolve) => setTimeout(resolve, 1500));
// Simulate an error for demonstration
if (Math.random() > 0.7) {
throw new Error('Failed to fetch data!');
}
}
},
{
title: 'Subtask 2.2: Transforming data',
task: async (_, subtask) => {
subtask.output = 'Applying transformations.';
await new Promise((resolve) => setTimeout(resolve, 800));
}
}
],
{ concurrent: true, exitOnError: false }
)
},
{
title: 'Main task 3: Generating output',
task: async (ctx, task) => {
ctx.output = `Processed: ${ctx.input.toUpperCase()}`;
task.output = `Final output: ${ctx.output}`;
await new Promise((resolve) => setTimeout(resolve, 500));
},
options: { persistentOutput: true }
}
],
{
concurrent: false,
exitOnError: true,
renderer: ListrDefaultRenderer,
rendererOptions: {
collapse: false,
clearOutput: true
}
}
);
try {
const context = await tasks.run();
console.log('\nAll tasks completed successfully!');
console.log('Final Context:', context);
} catch (e: any) {
console.error('\nOne or more tasks failed:', e.message);
}
}
runTasks();