Clack Guidelines
Overview
This skill provides guidance for building beautiful interactive command-line interfaces using Clack. It covers common patterns for text input, selections, autocomplete, progress tracking, streaming output, and creating complete interactive CLI applications.
Quick Start
Installation
bash1npm install @clack/prompts
Basic Program
javascript1import { text, isCancel, cancel } from "@clack/prompts"; 2 3const projectPath = await text({ 4 message: "Where should we create your project?", 5 placeholder: "./my-awesome-project", 6 initialValue: "./sparkling-solid", 7 validate: (value) => { 8 if (!value) return "Please enter a path."; 9 if (value[0] !== ".") return "Please enter a relative path."; 10 }, 11}); 12 13if (isCancel(projectPath)) { 14 cancel("Operation cancelled."); 15 process.exit(0); 16} 17 18console.log(`Creating project at: ${projectPath}`);
Complete Interactive CLI
javascript1import * as p from "@clack/prompts"; 2import color from "picocolors"; 3 4async function createApp() { 5 console.clear(); 6 p.intro(color.bgCyan(color.black(" create-myapp "))); 7 8 const config = await p.group( 9 { 10 name: () => 11 p.text({ 12 message: "What is your project name?", 13 placeholder: "my-awesome-app", 14 validate: (value) => { 15 if (!value) return "Project name is required"; 16 }, 17 }), 18 19 framework: () => 20 p.select({ 21 message: "Choose your framework", 22 options: [ 23 { value: "react", label: "React", hint: "Popular" }, 24 { value: "vue", label: "Vue" }, 25 { value: "svelte", label: "Svelte" }, 26 ], 27 }), 28 29 typescript: () => 30 p.confirm({ 31 message: "Use TypeScript?", 32 initialValue: true, 33 }), 34 }, 35 { 36 onCancel: () => { 37 p.cancel("Setup cancelled"); 38 process.exit(0); 39 }, 40 }, 41 ); 42 43 console.log(config); 44} 45 46createApp().catch(console.error);
Common Patterns
Text Input
Single-line text input with validation and placeholders.
javascript1import { text } from "@clack/prompts"; 2 3const name = await text({ 4 message: "Enter your name", 5 placeholder: "John Doe", 6 validate: (value) => { 7 if (!value) return "Name is required"; 8 }, 9});
Password Input
Secure password input with masking.
javascript1import { password } from "@clack/prompts"; 2 3const userPassword = await password({ 4 message: "Provide a password", 5 mask: "•", 6 validate: (value) => { 7 if (!value) return "Please enter a password."; 8 if (value.length < 8) return "Password should have at least 8 characters."; 9 }, 10});
Autocomplete Single Selection
Type-ahead search with real-time filtering.
javascript1import { autocomplete } from "@clack/prompts"; 2 3const country = await autocomplete({ 4 message: "Select a country", 5 options: [ 6 { value: "us", label: "United States", hint: "NA" }, 7 { value: "ca", label: "Canada", hint: "NA" }, 8 { value: "uk", label: "United Kingdom", hint: "EU" }, 9 ], 10 placeholder: "Type to search countries...", 11 maxItems: 8, 12 initialValue: "us", 13});
Autocomplete Multi-Select
Type-ahead search with multi-selection.
javascript1import { autocompleteMultiselect } from "@clack/prompts"; 2 3const frameworks = await autocompleteMultiselect({ 4 message: "Select frameworks (type to filter)", 5 options: [ 6 { value: "react", label: "React", hint: "Frontend/UI" }, 7 { value: "vue", label: "Vue.js", hint: "Frontend/UI" }, 8 { value: "nextjs", label: "Next.js", hint: "React Framework" }, 9 ], 10 placeholder: "Type to filter...", 11 maxItems: 8, 12 initialValues: ["react", "nextjs"], 13 required: true, 14});
Single Selection Menu
Choose one option from a scrollable list.
javascript1import { select } from "@clack/prompts"; 2 3const projectType = await select({ 4 message: "Pick a project type", 5 initialValue: "ts", 6 maxItems: 5, 7 options: [ 8 { value: "ts", label: "TypeScript" }, 9 { value: "js", label: "JavaScript" }, 10 { value: "rust", label: "Rust" }, 11 { value: "go", label: "Go" }, 12 ], 13});
Multi-Select Menu
Select multiple options from a list.
javascript1import { multiselect } from "@clack/prompts"; 2 3const tools = await multiselect({ 4 message: "Select additional tools", 5 initialValues: ["prettier", "eslint"], 6 required: true, 7 options: [ 8 { value: "prettier", label: "Prettier", hint: "recommended" }, 9 { value: "eslint", label: "ESLint", hint: "recommended" }, 10 { value: "stylelint", label: "Stylelint" }, 11 ], 12});
Confirmation Dialog
Binary yes/no choice.
javascript1import { confirm } from "@clack/prompts"; 2 3const shouldInstallDeps = await confirm({ 4 message: "Install dependencies?", 5 active: "Yes, please", 6 inactive: "No, skip", 7 initialValue: false, 8});
Progress Bar
Visual progress indicator for long-running operations.
javascript1import { progress } from "@clack/prompts"; 2import { setTimeout } from "node:timers/promises"; 3 4const downloadProgress = progress({ 5 style: "block", 6 max: 100, 7 size: 40, 8}); 9 10downloadProgress.start("Downloading packages"); 11 12for (let i = 0; i < 100; i += 10) { 13 await setTimeout(500); 14 downloadProgress.advance(10); 15 downloadProgress.message(`Downloaded ${i + 10}%`); 16} 17 18downloadProgress.stop("Download complete!");
Spinner Loading Indicator
Display progress for long-running operations.
javascript1import { spinner } from "@clack/prompts"; 2import { setTimeout } from "node:timers/promises"; 3 4const s = spinner(); 5 6s.start("Installing packages via pnpm"); 7await setTimeout(2000); 8 9s.message("Downloading dependencies"); 10await setTimeout(1500); 11 12s.stop("Installation complete!");
Task Log with Live Output
Display streaming output that clears on success and persists on error.
javascript1import { taskLog } from "@clack/prompts"; 2import { spawn } from "node:child_process"; 3 4const log = taskLog({ 5 title: "Running npm install", 6 limit: 10, 7 retainLog: true, 8 spacing: 1, 9}); 10 11const npmInstall = spawn("npm", ["install"]); 12 13npmInstall.stdout.on("data", (data) => { 14 log.message(data.toString(), { raw: true }); 15}); 16 17npmInstall.on("close", (code) => { 18 if (code === 0) { 19 log.success("Installation complete!"); 20 } else { 21 log.error("Installation failed!", { showLog: true }); 22 } 23});
Streaming Output for LLMs
Stream dynamic content from async iterables.
javascript1import { stream } from "@clack/prompts"; 2import { setTimeout } from "node:timers/promises"; 3 4await stream.step( 5 (async function* () { 6 const words = "Building your application with modern tools".split(" "); 7 for (const word of words) { 8 yield word; 9 yield " "; 10 await setTimeout(100); 11 } 12 })(), 13);
Grouped Prompts with Sequential Execution
Execute multiple prompts in sequence with shared state.
javascript1import * as p from "@clack/prompts"; 2import color from "picocolors"; 3 4p.intro(color.bgCyan(color.black(" create-app "))); 5 6const project = await p.group( 7 { 8 path: () => 9 p.text({ 10 message: "Where should we create your project?", 11 placeholder: "./sparkling-solid", 12 validate: (value) => { 13 if (!value) return "Please enter a path."; 14 if (value[0] !== ".") return "Please enter a relative path."; 15 }, 16 }), 17 18 type: ({ results }) => 19 p.select({ 20 message: `Pick a project type within "${results.path}"`, 21 initialValue: "ts", 22 options: [ 23 { value: "ts", label: "TypeScript" }, 24 { value: "js", label: "JavaScript" }, 25 { value: "rust", label: "Rust" }, 26 ], 27 }), 28 29 tools: () => 30 p.multiselect({ 31 message: "Select additional tools", 32 options: [ 33 { value: "prettier", label: "Prettier", hint: "recommended" }, 34 { value: "eslint", label: "ESLint", hint: "recommended" }, 35 ], 36 }), 37 38 install: () => 39 p.confirm({ 40 message: "Install dependencies?", 41 initialValue: true, 42 }), 43 }, 44 { 45 onCancel: () => { 46 p.cancel("Operation cancelled."); 47 process.exit(0); 48 }, 49 }, 50); 51 52console.log(project.path, project.type, project.tools, project.install);
Task Execution with Spinners
Run multiple tasks with automatic spinner management.
javascript1import * as p from "@clack/prompts"; 2import { setTimeout } from "node:timers/promises"; 3 4await p.tasks([ 5 { 6 title: "Installing dependencies", 7 task: async (message) => { 8 message("Downloading packages"); 9 await setTimeout(1000); 10 message("Building native modules"); 11 await setTimeout(500); 12 return "Installed 127 packages"; 13 }, 14 }, 15 { 16 title: "Running linter", 17 task: async () => { 18 // Run linter 19 return "No issues found"; 20 }, 21 }, 22]);
Logging Messages
Display formatted status messages with different severity levels.
javascript1import { log } from "@clack/prompts"; 2import color from "picocolors"; 3 4log.info("Starting build process..."); 5log.step("Compiling TypeScript"); 6log.success("Build completed successfully!"); 7log.warn("Some dependencies are outdated"); 8log.error("Failed to connect to database"); 9 10// Custom symbol 11log.message("Custom message", { 12 symbol: color.cyan("→"), 13});
Intro, Outro, and Notes
Frame your CLI sessions with beautiful headers.
javascript1import { intro, outro, note } from "@clack/prompts"; 2import color from "picocolors"; 3 4intro(color.bgCyan(color.black(" my-cli-tool v2.0.0 "))); 5 6// ... prompts and operations ... 7 8note("cd my-project\nnpm install\nnpm run dev", "Next steps"); 9 10outro( 11 `Problems? ${color.underline(color.cyan("https://github.com/myorg/myproject/issues"))}`, 12);
Custom Key Bindings
Configure alternative key mappings for navigation.
javascript1import { updateSettings } from "@clack/prompts"; 2 3// Enable WASD navigation 4updateSettings({ 5 aliases: { 6 w: "up", 7 s: "down", 8 a: "left", 9 d: "right", 10 }, 11});
Non-Interactive Mode Detection
Handle CI environments where prompts aren't supported.
javascript1import { text, block } from "@clack/prompts"; 2 3// In CI environments, prompts automatically use defaults 4const name = await text({ 5 message: "Project name?", 6 defaultValue: "default-project", 7}); 8 9// block() utility prevents input in non-TTY environments 10const unblock = block(); 11// Perform operations 12unblock();
Cancellation and Error Handling
Robust error handling for user cancellations and validation errors.
javascript1import * as p from "@clack/prompts"; 2 3async function setupProject() { 4 try { 5 const responses = await p.group({ 6 name: () => 7 p.text({ 8 message: "Project name?", 9 validate: (v) => { 10 if (!v) return "Required"; 11 }, 12 }), 13 14 confirm: ({ results }) => 15 p.confirm({ 16 message: `Create "${results.name}"?`, 17 }), 18 }); 19 20 // Check if any step was cancelled 21 if (p.isCancel(responses.name) || p.isCancel(responses.confirm)) { 22 p.cancel("Setup cancelled"); 23 return; 24 } 25 26 if (!responses.confirm) { 27 p.outro("Setup aborted"); 28 return; 29 } 30 31 // Proceed with setup 32 p.outro("Project created!"); 33 } catch (error) { 34 p.log.error(`Setup failed: ${error.message}`); 35 process.exit(1); 36 } 37} 38 39setupProject();
Advanced Features
Custom Render Function with @clack/core
Build custom prompts with full control over rendering.
javascript1import { TextPrompt } from "@clack/core"; 2import color from "picocolors"; 3 4const customPrompt = new TextPrompt({ 5 validate: (value) => (value.length < 3 ? "Too short!" : undefined), 6 render() { 7 const title = `>>> ${color.bold("Enter your name")}:`; 8 const input = this.valueWithCursor || color.dim("(empty)"); 9 10 switch (this.state) { 11 case "error": 12 return `${title}\n${color.red(input)}\n${color.red(this.error)}`; 13 case "submit": 14 return `${title} ${color.green(this.value)}`; 15 case "cancel": 16 return `${title} ${color.strikethrough(this.value)}`; 17 default: 18 return `${title}\n${color.cyan(input)}`; 19 } 20 }, 21}); 22 23const result = await customPrompt.prompt(); 24console.log(`Result: ${result}`);
Event-Driven Custom Prompt
Subscribe to prompt events for advanced interactions.
javascript1import { TextPrompt } from "@clack/core"; 2 3const prompt = new TextPrompt({ 4 render() { 5 return `Input: ${this.valueWithCursor}`; 6 }, 7}); 8 9// Listen to events 10prompt.on("value", (value) => { 11 console.log(`Current value: ${value}`); 12}); 13 14prompt.on("submit", () => { 15 console.log("User submitted the form"); 16}); 17 18prompt.on("cancel", () => { 19 console.log("User cancelled"); 20}); 21 22const result = await prompt.prompt();
Complete CLI Application Example
Full-featured setup wizard combining multiple prompt types.
javascript1import { setTimeout } from "node:timers/promises"; 2import * as p from "@clack/prompts"; 3import color from "picocolors"; 4 5async function createApp() { 6 console.clear(); 7 8 p.intro(color.bgCyan(color.black(" create-myapp "))); 9 10 const config = await p.group( 11 { 12 name: () => 13 p.text({ 14 message: "What is your project name?", 15 placeholder: "my-awesome-app", 16 validate: (value) => { 17 if (!value) return "Project name is required"; 18 if (!/^[a-z0-9-]+$/.test(value)) { 19 return "Use only lowercase letters, numbers, and hyphens"; 20 } 21 }, 22 }), 23 24 framework: () => 25 p.select({ 26 message: "Choose your framework", 27 options: [ 28 { value: "react", label: "React", hint: "Popular" }, 29 { value: "vue", label: "Vue" }, 30 { value: "svelte", label: "Svelte" }, 31 { value: "solid", label: "Solid" }, 32 ], 33 }), 34 35 features: () => 36 p.multiselect({ 37 message: "Select features", 38 required: false, 39 options: [ 40 { value: "router", label: "Router" }, 41 { value: "state", label: "State Management" }, 42 { value: "testing", label: "Testing Setup" }, 43 { value: "ci", label: "CI/CD Pipeline" }, 44 ], 45 }), 46 47 typescript: () => 48 p.confirm({ 49 message: "Use TypeScript?", 50 initialValue: true, 51 }), 52 53 install: () => 54 p.confirm({ 55 message: "Install dependencies now?", 56 initialValue: true, 57 }), 58 }, 59 { 60 onCancel: () => { 61 p.cancel("Setup cancelled"); 62 process.exit(0); 63 }, 64 }, 65 ); 66 67 // Execute setup tasks 68 await p.tasks([ 69 { 70 title: "Creating project structure", 71 task: async () => { 72 // Create project files 73 return `Created ${config.name}`; 74 }, 75 }, 76 { 77 title: "Installing dependencies", 78 task: async (message) => { 79 message("Resolving packages..."); 80 await setTimeout(1000); 81 message("Downloading..."); 82 await setTimeout(2000); 83 return "Installed 42 packages"; 84 }, 85 enabled: config.install, 86 }, 87 ]); 88 89 // Display next steps 90 const nextSteps = [ 91 `cd ${config.name}`, 92 config.install ? "" : "npm install", 93 "npm run dev", 94 ] 95 .filter(Boolean) 96 .join("\n"); 97 98 p.note(nextSteps, "Next steps"); 99 100 p.outro( 101 `All set! ${color.underline(color.cyan("https://docs.example.com"))}`, 102 ); 103} 104 105async function createProjectFiles(config) { 106 // Implementation... 107} 108 109createApp().catch(console.error);
Detailed API Reference
For comprehensive API documentation including all prompt types, configuration options, and advanced features, see the detailed API reference:
This reference includes:
- Complete prompt type documentation
- Configuration options for each prompt
- Validation and error handling patterns
- Streaming and async patterns
- Custom key bindings and settings
- Integration with @clack/core
- TypeScript support
- And more
Resources
references/
This skill includes detailed API documentation:
- clack-api.md - Comprehensive API reference for Clack prompts, organized by prompt type with code examples for each feature.