Skip to main content
Technology & EngineeringCli Development183 lines

Clack Prompts

Build beautiful interactive CLI prompts using @clack/prompts with minimal boilerplate

Quick Summary17 lines
You are an expert in building interactive CLI prompts with @clack/prompts, a library for creating polished, user-friendly terminal interactions.

## Key Points

- Always handle cancellation with `p.isCancel()` or the `onCancel` handler in `p.group()` — pressing Ctrl+C returns a cancel symbol rather than throwing, so unchecked results lead to subtle bugs.
- Use `p.group()` to collect related prompts together; it handles the flow, cancellation, and gives access to previous answers via `results`.
- Provide `placeholder` text and `hint` strings to guide users — they appear as dimmed text and help users understand expected input formats.
- **Omitting placeholders and hints** — empty input fields with no guidance leave users guessing what format is expected; always provide placeholder text and hint strings.
- Mixing `console.log` with clack output breaks the visual formatting — use `p.log.info()`, `p.log.warn()`, etc. instead.

## Quick Example

```bash
npm install @clack/prompts
```
skilldb get cli-development-skills/Clack PromptsFull skill: 183 lines
Paste into your CLAUDE.md or agent config

@clack/prompts — CLI Development

You are an expert in building interactive CLI prompts with @clack/prompts, a library for creating polished, user-friendly terminal interactions.

Overview

@clack/prompts provides a set of high-level prompt components (text input, select, multiselect, confirm, spinner) with a cohesive visual style. It is designed for quick, attractive CLI wizards and setup flows, and is the prompt library used by tools like the SvelteKit and Astro CLIs.

Setup & Configuration

Install the package:

npm install @clack/prompts

Basic prompt flow:

import * as p from '@clack/prompts';

async function main() {
  p.intro('create-my-app');

  const project = await p.group(
    {
      name: () =>
        p.text({
          message: 'Project name',
          placeholder: 'my-app',
          validate: (value) => {
            if (!value) return 'Name is required';
            if (!/^[a-z0-9-]+$/.test(value)) return 'Use lowercase letters, numbers, and hyphens only';
          },
        }),
      framework: () =>
        p.select({
          message: 'Pick a framework',
          options: [
            { value: 'react', label: 'React' },
            { value: 'vue', label: 'Vue' },
            { value: 'svelte', label: 'Svelte' },
          ],
        }),
      features: () =>
        p.multiselect({
          message: 'Select features',
          options: [
            { value: 'typescript', label: 'TypeScript', hint: 'recommended' },
            { value: 'eslint', label: 'ESLint' },
            { value: 'testing', label: 'Testing' },
          ],
          required: false,
        }),
      install: () =>
        p.confirm({
          message: 'Install dependencies?',
          initialValue: true,
        }),
    },
    {
      onCancel: () => {
        p.cancel('Setup cancelled.');
        process.exit(0);
      },
    }
  );

  p.outro(`Created ${project.name} with ${project.framework}!`);
}

main();

Core Patterns

Spinner for async operations

import * as p from '@clack/prompts';

async function deploy() {
  const s = p.spinner();

  s.start('Building project');
  await buildProject();
  s.stop('Build complete');

  s.start('Deploying to production');
  await deployToProduction();
  s.stop('Deployed successfully');
}

Conditional prompts within a group

const config = await p.group({
  database: () =>
    p.select({
      message: 'Database',
      options: [
        { value: 'postgres', label: 'PostgreSQL' },
        { value: 'sqlite', label: 'SQLite' },
        { value: 'none', label: 'None' },
      ],
    }),
  connectionString: ({ results }) => {
    if (results.database === 'none') return;
    return p.text({
      message: 'Connection string',
      placeholder:
        results.database === 'postgres'
          ? 'postgresql://localhost:5432/mydb'
          : './data.db',
    });
  },
});

Individual prompts outside groups

const shouldContinue = await p.confirm({
  message: 'This will overwrite existing files. Continue?',
});

if (p.isCancel(shouldContinue) || !shouldContinue) {
  p.cancel('Aborted.');
  process.exit(0);
}

Logging utilities

p.log.info('Configuration loaded from ~/.myrc');
p.log.warn('No lockfile found — dependencies may drift');
p.log.error('Failed to connect to database');
p.log.success('All checks passed');
p.log.step('Installing dependencies');

// Notes for structured messages
p.note('Run `npm run dev` to start the dev server', 'Next steps');

Best Practices

  • Always handle cancellation with p.isCancel() or the onCancel handler in p.group() — pressing Ctrl+C returns a cancel symbol rather than throwing, so unchecked results lead to subtle bugs.
  • Use p.group() to collect related prompts together; it handles the flow, cancellation, and gives access to previous answers via results.
  • Provide placeholder text and hint strings to guide users — they appear as dimmed text and help users understand expected input formats.

Core Philosophy

CLI prompts are a conversation with the user, and @clack/prompts makes that conversation feel polished and intentional. The library's visual style communicates professionalism — use it to guide users through complex setup flows with clear steps, helpful placeholders, and graceful cancellation. A well-designed prompt flow should feel like a wizard, not an interrogation.

Always handle cancellation. Users press Ctrl+C for many reasons, and your CLI should respond gracefully every time. Clack returns a cancel symbol rather than throwing, which means unchecked results silently produce undefined values that cascade into confusing errors downstream. Check p.isCancel() on individual prompts or use the onCancel handler in p.group() to exit cleanly.

Let previous answers inform subsequent questions. The p.group() API gives each prompt access to results from prior steps, enabling conditional flows where irrelevant questions are skipped entirely. This reduces friction and makes the CLI feel intelligent rather than mechanical.

Anti-Patterns

  • Mixing console.log with clack output — writing directly to stdout while clack is rendering breaks the visual formatting and produces garbled output; use p.log.info(), p.log.warn(), and friends instead.

  • Not checking for cancellation after each prompt — when a user presses Ctrl+C, clack returns a cancel symbol rather than throwing; proceeding without checking produces TypeError crashes or corrupted output.

  • Asking unnecessary questions upfront — prompting for a database connection string when the user selected "no database" wastes their time; use conditional prompts in p.group() to skip irrelevant steps.

  • Omitting placeholders and hints — empty input fields with no guidance leave users guessing what format is expected; always provide placeholder text and hint strings.

  • Using clack for non-interactive environments — clack prompts require a TTY; running them in CI or piped input contexts hangs indefinitely; detect process.stdin.isTTY and fall back to argument-based configuration.

Common Pitfalls

  • Forgetting that p.multiselect with required: false returns an empty array when nothing is selected, but with required: true (default) it forces at least one selection — know which behavior you want.
  • Mixing console.log with clack output breaks the visual formatting — use p.log.info(), p.log.warn(), etc. instead.

Install this skill directly: skilldb add cli-development-skills

Get CLI access →