Inquirer.js

A collection of common interactive command line user interfaces. Makes it easy to create beautiful CLI prompts.

javascriptnodejsclicommand-linepromptsinteractive

Framework

Inquirer.js

Overview

Inquirer.js is a comprehensive library for creating interactive command-line user interfaces in Node.js. It makes it easy to create beautiful interactive prompts and provides various types of prompts including input, selection, confirmation, password input, and more. It has been adopted by many prominent projects (npm init, create-react-app, Yeoman, etc.) and has become the de facto standard for CLI prompts in the Node.js ecosystem.

Why Inquirer.js is chosen:

  • Rich prompt types: Over 11 types including input, confirm, list, checkbox, password, editor
  • Modular design: Install only the features you need (@inquirer/prompts)
  • Full TypeScript support: Type safety and excellent development experience with IntelliSense
  • Advanced customization: Theme, validation, and filtering capabilities
  • Async support: async/await and Promise-based API

Details

History and Evolution

Inquirer.js was started by Simon Boudrias in 2013 and has been beloved by the Node.js community for over 10 years. In 2024, a major rearchitecture was performed, introducing the modular @inquirer/prompts package. This new design enables bundle size optimization and independent use of individual prompts.

Position in the Ecosystem

Positioned as one of the most important libraries for interactive CLI development in Node.js:

  • npm ecosystem: Foundation technology for npm init and create-react-app
  • Development tools: Widely adopted in Yeoman, Angular CLI, Vue CLI, etc.
  • Enterprise tools: Used in many internal corporate tools and CI/CD pipelines
  • Education: Frequently introduced as introductory material for Node.js learning

Latest Trends (2024-2025)

  • Migration to modular design: Lightweight and tree-shaking support with @inquirer/prompts
  • Performance improvements: Faster performance with new core architecture
  • React-like hooks: Modern APIs like useState, useEffect, useKeypress
  • ESM support: Full support for ES Modules
  • AbortController support: Prompt cancellation functionality
  • Enhanced testing support: Unit testing support with @inquirer/testing package

Key Features

Core Prompt Types

  • input: Text input prompt
  • number: Numeric input prompt (with automatic validation)
  • confirm: Yes/no confirmation prompt
  • list/select: Single selection list prompt
  • rawlist: Numeric key selection prompt
  • expand: Compact selection prompt (with keyboard shortcuts)
  • checkbox: Multiple selection checkbox prompt
  • password: Password input prompt (masked display)
  • editor: External editor launch prompt

Advanced Features

  • search: Searchable selection prompt (with async data source support)
  • autocomplete: Prompt with autocomplete functionality
  • table: Table-format multiple selection prompt
  • file-selector: File/directory selection prompt
  • datepicker: Date selection prompt

Developer Experience

  • TypeScript type inference: Automatic inference of prompt return value types
  • Validation: Synchronous and asynchronous validation functions
  • Filtering: Input value transformation and normalization
  • Conditional branching: Dynamic question display based on previous answers
  • Theme support: Customization of colors, prefixes, and styles
  • i18n support: Multi-language message support

Pros and Cons

Pros

  • Rich prompt types: Easy implementation of various UI elements
  • Excellent UX: Intuitive and beautiful interface
  • Modular design: Minimal bundle size
  • Full TypeScript support: Type safety and development efficiency
  • Rich ecosystem: Abundance of plugins and themes
  • Active community: Continuous development and support
  • Testing support: Comprehensive testing capabilities

Cons

  • Learning curve: Advanced features require familiarization
  • Performance: Overkill for very simple prompts
  • Dependencies: May depend on other libraries
  • Configuration complexity: Advanced customization requires detailed configuration

Key Links

Usage Examples

Basic Usage (New API)

import { input, confirm, select } from '@inquirer/prompts';

// Text input
const name = await input({ message: 'Enter your name' });

// Confirmation prompt
const confirmed = await confirm({ message: 'Do you want to continue?' });

// Selection prompt
const framework = await select({
  message: 'Select a framework',
  choices: [
    { name: 'React', value: 'react' },
    { name: 'Vue', value: 'vue' },
    { name: 'Angular', value: 'angular' }
  ]
});

console.log(`Hello, ${name}!`);
console.log(`Selected framework: ${framework}`);

Combining Multiple Prompts

import { input, select, checkbox, confirm } from '@inquirer/prompts';

async function setupProject() {
  const projectName = await input({
    message: 'Enter project name',
    validate: (input) => {
      if (input.length < 3) {
        return 'Project name must be at least 3 characters long';
      }
      return true;
    }
  });

  const projectType = await select({
    message: 'Select project type',
    choices: [
      { name: 'Web Application', value: 'web' },
      { name: 'CLI Tool', value: 'cli' },
      { name: 'Library', value: 'library' }
    ]
  });

  const features = await checkbox({
    message: 'Select required features',
    choices: [
      { name: 'TypeScript', value: 'typescript' },
      { name: 'ESLint', value: 'eslint' },
      { name: 'Prettier', value: 'prettier' },
      { name: 'Jest', value: 'jest' },
      { name: 'Husky (Git hooks)', value: 'husky' }
    ]
  });

  const useGit = await confirm({
    message: 'Initialize Git repository?',
    default: true
  });

  return {
    projectName,
    projectType,
    features,
    useGit
  };
}

// Usage example
const config = await setupProject();
console.log('Project configuration:', config);

Validation and Filtering Examples

import { input, number } from '@inquirer/prompts';

// Synchronous validation
const email = await input({
  message: 'Enter your email address',
  validate: (input) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(input)) {
      return 'Please enter a valid email address';
    }
    return true;
  }
});

// Asynchronous validation
const username = await input({
  message: 'Enter username',
  validate: async (input) => {
    if (input.length < 3) {
      return 'Username must be at least 3 characters long';
    }
    
    // External API duplicate check (example)
    try {
      const response = await fetch(`/api/users/check/${input}`);
      const { available } = await response.json();
      if (!available) {
        return 'This username is already taken';
      }
    } catch (error) {
      return 'Failed to verify username';
    }
    
    return true;
  }
});

// Number input (automatic validation)
const age = await number({
  message: 'Enter your age',
  min: 0,
  max: 150,
  validate: (input) => {
    if (input < 18) {
      return 'You must be at least 18 years old';
    }
    return true;
  }
});

Search Prompt Example

import { search } from '@inquirer/prompts';

const selectedPackage = await search({
  message: 'Search for npm packages',
  source: async (input, { signal }) => {
    // Return empty array if no input
    if (!input) {
      return [];
    }

    try {
      // Search from npm API
      const response = await fetch(
        `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(input)}&size=10`,
        { signal }
      );
      
      if (!response.ok) {
        return [];
      }

      const data = await response.json();
      
      return data.objects.map((pkg) => ({
        name: pkg.package.name,
        value: pkg.package.name,
        description: pkg.package.description
      }));
    } catch (error) {
      // Error handling for request cancellation, etc.
      if (error.name === 'AbortError') {
        return [];
      }
      throw error;
    }
  }
});

console.log('Selected package:', selectedPackage);

Password Prompt and Editor Example

import { password, editor, confirm } from '@inquirer/prompts';

// Password input
const userPassword = await password({
  message: 'Enter password',
  mask: '*',
  validate: (input) => {
    if (input.length < 8) {
      return 'Password must be at least 8 characters long';
    }
    if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(input)) {
      return 'Password must contain at least one lowercase letter, uppercase letter, and number';
    }
    return true;
  }
});

// Editor launch
const description = await editor({
  message: 'Enter detailed description (editor will open)',
  default: '# Project Description\n\nWrite detailed description here...',
  validate: (input) => {
    if (input.trim().length === 0) {
      return 'Please enter a description';
    }
    return true;
  }
});

// Confirmation
const saveToFile = await confirm({
  message: 'Save configuration to file?',
  default: true
});

if (saveToFile) {
  console.log('Configuration saved');
}

Theme Customization Example

import { input, select } from '@inquirer/prompts';
import colors from 'picocolors';

// Using custom theme
const customTheme = {
  prefix: {
    idle: colors.blue('?'),
    done: colors.green('✓')
  },
  spinner: {
    interval: 80,
    frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
  },
  style: {
    answer: (text) => colors.cyan(text),
    message: (text, status) => {
      if (status === 'done') {
        return colors.bold(text);
      }
      return text;
    },
    error: (text) => colors.red(`✗ ${text}`),
    help: (text) => colors.dim(text),
    highlight: (text) => colors.yellow(text),
    key: (text) => colors.cyan(`<${text}>`)
  }
};

const name = await input({
  message: 'Enter project name',
  theme: customTheme
});

const framework = await select({
  message: 'Select a framework',
  choices: [
    { name: 'React', value: 'react' },
    { name: 'Vue.js', value: 'vue' },
    { name: 'Angular', value: 'angular' }
  ],
  theme: customTheme
});

Conditional Branching and Error Handling Example

import { input, confirm, select } from '@inquirer/prompts';

async function interactiveSetup() {
  try {
    const projectType = await select({
      message: 'Select project type',
      choices: [
        { name: 'Web Application', value: 'web' },
        { name: 'Mobile App', value: 'mobile' },
        { name: 'Desktop App', value: 'desktop' }
      ]
    });

    let framework;
    // Conditional branching based on project type
    if (projectType === 'web') {
      framework = await select({
        message: 'Select web framework',
        choices: [
          { name: 'React', value: 'react' },
          { name: 'Vue.js', value: 'vue' },
          { name: 'Angular', value: 'angular' },
          { name: 'Svelte', value: 'svelte' }
        ]
      });
    } else if (projectType === 'mobile') {
      framework = await select({
        message: 'Select mobile framework',
        choices: [
          { name: 'React Native', value: 'react-native' },
          { name: 'Flutter', value: 'flutter' },
          { name: 'Ionic', value: 'ionic' }
        ]
      });
    }

    const useTypeScript = await confirm({
      message: 'Use TypeScript?',
      default: true
    });

    // Additional questions based on configuration
    let testFramework;
    if (useTypeScript) {
      testFramework = await select({
        message: 'Select test framework',
        choices: [
          { name: 'Jest', value: 'jest' },
          { name: 'Vitest', value: 'vitest' },
          { name: 'Mocha + Chai', value: 'mocha' }
        ]
      });
    }

    return {
      projectType,
      framework,
      useTypeScript,
      testFramework
    };

  } catch (error) {
    if (error.name === 'ExitPromptError') {
      console.log('\n👋 Setup was cancelled');
      process.exit(0);
    }
    throw error;
  }
}

// Timeout using AbortController
const controller = new AbortController();
setTimeout(() => {
  controller.abort();
}, 30000); // 30 second timeout

try {
  const config = await interactiveSetup();
  console.log('✅ Setup complete:', config);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('⏰ Setup timed out');
  } else {
    console.error('❌ An error occurred:', error.message);
  }
}