Clack Prompts
Build beautiful interactive CLI prompts using @clack/prompts with minimal boilerplate
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@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 theonCancelhandler inp.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 viaresults. - Provide
placeholdertext andhintstrings 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.logwith clack output — writing directly to stdout while clack is rendering breaks the visual formatting and produces garbled output; usep.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.isTTYand fall back to argument-based configuration.
Common Pitfalls
- Forgetting that
p.multiselectwithrequired: falsereturns an empty array when nothing is selected, but withrequired: true(default) it forces at least one selection — know which behavior you want. - Mixing
console.logwith clack output breaks the visual formatting — usep.log.info(),p.log.warn(), etc. instead.
Install this skill directly: skilldb add cli-development-skills
Related Skills
Chalk Picocolors
Style terminal output with chalk and picocolors for colored, formatted CLI text
CLI Distribution
Package and distribute CLI tools via npm/npx, standalone binaries with pkg, and Homebrew taps
CLI Testing
Test CLI applications using subprocess execution, mock filesystems, and snapshot testing
Commander Js
Build structured CLI applications with Commander.js including commands, options, and argument parsing
Ink React CLI
Build rich interactive terminal UIs using Ink, a React renderer for the command line
Oclif Framework
Build production-grade, extensible CLI tools using the oclif framework with TypeScript