···33description: Learn how to get started with Clack
44---
5566-import { Tabs, TabItem } from '@astrojs/starlight/components';
66+import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
7788Clack is a modern, flexible and powerful CLI library that helps you build beautiful command-line interfaces with ease. It provides a set of high-level components and low-level primitives that make it simple to create interactive command-line applications.
99···1717- 🎭 Form validation
1818- 🎯 Error handling
1919- 🎨 Consistent styling
2020+- 📦 ESM-first distribution
20212122## Installation
2223···8788Clack provides several high-level components that make it easy to build interactive CLIs:
88898990- `text()` - For text input with validation
9191+- `password()` - For secure password input with masking
9092- `select()` - For selection menus
9193- `confirm()` - For yes/no confirmations
9294- `multiselect()` - For multiple selections
9595+- `groupMultiselect()` - For grouped multiple selections
9696+- `autocomplete()` - For searchable selection menus
9797+- `path()` - For file/directory path selection with autocomplete
9398- `note()` - For displaying information
9999+- `box()` - For boxed text display
94100- `spinner()` - For loading states
101101+- `progress()` - For progress bar display
102102+- `tasks()` - For sequential task execution
103103+- `taskLog()` - For log output that clears on success
9510496105### Low-Level Primitives
97106···102111103112const p = new TextPrompt({
104113 render() {
105105- return `What's your name?\n${this.valueWithCursor}`;
114114+ return `What's your name?\n${this.value ?? ''}`;
106115 },
107116});
108117···129138// TypeScript will ensure the validation function returns the correct type
130139const age = await text({
131140 message: 'Enter your age:',
132132- validate: (value: string) => {
141141+ validate: (value) => {
142142+ if (!value) return 'Please enter a value';
133143 const num = parseInt(value);
134144 if (isNaN(num)) return 'Please enter a valid number';
135145 if (num < 0 || num > 120) return 'Age must be between 0 and 120';
+8-4
src/content/docs/clack/guides/best-practices.mdx
···3939import { text } from '@clack/prompts';
40404141// Define validation function outside for reusability
4242-function validateAge(value: string): string | undefined {
4242+function validateAge(value: string | undefined): string | undefined {
4343+ if (!value) return 'Please enter a value';
4344 const num = parseInt(value);
4445 if (isNaN(num)) return 'Please enter a valid number';
4546 if (num < 0 || num > 120) return 'Age must be between 0 and 120';
···134135}
135136136137// Define validation functions
137137-function validateProjectName(value: string): string | undefined {
138138- if (value.length === 0) return 'Name is required';
138138+function validateProjectName(value: string | undefined): string | undefined {
139139+ if (!value || value.length === 0) return 'Name is required';
139140 if (!/^[a-z0-9-]+$/.test(value)) return 'Name can only contain lowercase letters, numbers, and hyphens';
140141 return undefined;
141142}
···289290290291const serverPort = await text({
291292 message: 'Enter port number:',
292292- validate: (value: string) => {
293293+ validate: (value) => {
294294+ if (!value) return 'Please enter a value';
293295 const num = parseInt(value);
294296 if (isNaN(num)) return 'Please enter a valid number';
295297 if (num < 1 || num > 65535) return 'Port must be between 1 and 65535';
···349351 message: 'Database port:',
350352 defaultValue: dbType === 'postgres' ? '5432' : '3306',
351353 validate: (value) => {
354354+ if (!value) return 'Please enter a value';
352355 const num = parseInt(value);
353356 if (isNaN(num)) return 'Please enter a valid number';
354357 if (num < 1 || num > 65535) return 'Port must be between 1 and 65535';
···381384 message: 'Enter port number:',
382385 defaultValue: defaultPort,
383386 validate: (value) => {
387387+ if (!value) return 'Please enter a value';
384388 const num = parseInt(value);
385389 if (isNaN(num)) return 'Please enter a valid number';
386390 if (num < 1 || num > 65535) return 'Port must be between 1 and 65535';
+218-2
src/content/docs/clack/guides/examples.mdx
···142142 const name = await text({
143143 message: 'Project name:',
144144 validate: (value) => {
145145- if (value.length === 0) return 'Name is required';
145145+ if (!value || value.length === 0) return 'Name is required';
146146 if (!/^[a-z0-9-]+$/.test(value)) return 'Name can only contain lowercase letters, numbers, and hyphens';
147147 return undefined;
148148 },
···248248 message: 'Enter port number:',
249249 placeholder: '3000',
250250 validate: (value) => {
251251+ if (!value) return 'Please enter a value';
251252 const num = parseInt(value);
252253 if (isNaN(num)) return 'Please enter a valid number';
253254 if (num < 1 || num > 65535) return 'Port must be between 1 and 65535';
···570571 const name = await text({
571572 message: 'Full name:',
572573 validate: (value) => {
573573- if (value.length < 2) return 'Name must be at least 2 characters';
574574+ if (!value || value.length < 2) return 'Name must be at least 2 characters';
574575 if (!/^[a-zA-Z\s]*$/.test(value)) return 'Name can only contain letters and spaces';
575576 return undefined;
576577 },
···585586 const email = await text({
586587 message: 'Email address:',
587588 validate: (value) => {
589589+ if (!value) return 'Email is required';
588590 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
589591 if (!emailRegex.test(value)) return 'Please enter a valid email address';
590592 return undefined;
···600602 const ageInput = await text({
601603 message: 'Age:',
602604 validate: (value) => {
605605+ if (!value) return 'Age is required';
603606 const num = parseInt(value);
604607 if (isNaN(num)) return 'Please enter a valid number';
605608 if (num < 18 || num > 100) return 'Age must be between 18 and 100';
···712715 language,
713716 },
714717 };
718718+}
719719+```
720720+721721+## New Features Examples
722722+723723+### Path Selection
724724+725725+The `path` prompt provides file system navigation with autocomplete:
726726+727727+```ts twoslash
728728+import { path, isCancel } from '@clack/prompts';
729729+730730+async function selectConfigFile() {
731731+ const configPath = await path({
732732+ message: 'Select a configuration file:',
733733+ root: process.cwd(),
734734+ validate: (value) => {
735735+ if (!value?.endsWith('.json') && !value?.endsWith('.yaml')) {
736736+ return 'Please select a .json or .yaml file';
737737+ }
738738+ return undefined;
739739+ },
740740+ });
741741+742742+ if (isCancel(configPath)) {
743743+ console.log('Operation cancelled');
744744+ process.exit(0);
745745+ }
746746+747747+ console.log(`Selected config: ${configPath}`);
748748+}
749749+```
750750+751751+### Progress Bar
752752+753753+Display progress for long-running operations:
754754+755755+```ts twoslash
756756+import { progress, intro, outro } from '@clack/prompts';
757757+758758+async function downloadFiles() {
759759+ intro('File Downloader');
760760+761761+ const files = ['package.json', 'README.md', 'src/index.ts', 'tsconfig.json', 'LICENSE'];
762762+763763+ const prog = progress({
764764+ style: 'heavy',
765765+ max: files.length,
766766+ size: 30,
767767+ });
768768+769769+ prog.start('Downloading files');
770770+771771+ for (let i = 0; i < files.length; i++) {
772772+ // Simulate download
773773+ await new Promise(resolve => setTimeout(resolve, 500));
774774+ prog.advance(1, `Downloaded ${files[i]}`);
775775+ }
776776+777777+ prog.stop('All files downloaded');
778778+ outro('Download complete!');
779779+}
780780+```
781781+782782+### Spinner with Cancellation Handling
783783+784784+Handle graceful cancellation with the new spinner API:
785785+786786+```ts twoslash
787787+import { spinner, confirm, isCancel } from '@clack/prompts';
788788+789789+async function longRunningTask() {
790790+ const shouldStart = await confirm({
791791+ message: 'Start the long-running task?',
792792+ });
793793+794794+ if (isCancel(shouldStart) || !shouldStart) {
795795+ console.log('Task not started');
796796+ return;
797797+ }
798798+799799+ const spin = spinner({
800800+ onCancel: () => {
801801+ console.log('\nCleaning up resources...');
802802+ },
803803+ cancelMessage: 'Task was cancelled by user',
804804+ errorMessage: 'Task failed unexpectedly',
805805+ });
806806+807807+ spin.start('Processing data');
808808+809809+ try {
810810+ // Simulate work in stages
811811+ await new Promise(resolve => setTimeout(resolve, 1000));
812812+ spin.message('Analyzing results');
813813+814814+ await new Promise(resolve => setTimeout(resolve, 1000));
815815+ spin.message('Generating report');
816816+817817+ await new Promise(resolve => setTimeout(resolve, 1000));
818818+819819+ // Check if cancelled during processing
820820+ if (spin.isCancelled) {
821821+ return;
822822+ }
823823+824824+ spin.stop('Task completed successfully');
825825+ } catch (error) {
826826+ spin.error('An error occurred during processing');
827827+ }
828828+}
829829+```
830830+831831+### Task Log with Groups
832832+833833+Organize complex operations into groups:
834834+835835+```ts twoslash
836836+// @errors: 2345
837837+import { taskLog, intro, outro } from '@clack/prompts';
838838+839839+async function buildProject() {
840840+ intro('Project Builder');
841841+842842+ const log = taskLog({
843843+ title: 'Building project',
844844+ limit: 5, // Show last 5 lines per section
845845+ });
846846+847847+ // TypeScript compilation group
848848+ const tsGroup = log.group('TypeScript Compilation');
849849+ tsGroup.message('Checking types...');
850850+ await new Promise(resolve => setTimeout(resolve, 500));
851851+ tsGroup.message('Compiling src/index.ts');
852852+ await new Promise(resolve => setTimeout(resolve, 300));
853853+ tsGroup.message('Compiling src/utils.ts');
854854+ await new Promise(resolve => setTimeout(resolve, 300));
855855+ tsGroup.success('TypeScript compiled successfully');
856856+857857+ // Bundling group
858858+ const bundleGroup = log.group('Bundling');
859859+ bundleGroup.message('Reading entry points...');
860860+ await new Promise(resolve => setTimeout(resolve, 400));
861861+ bundleGroup.message('Resolving dependencies...');
862862+ await new Promise(resolve => setTimeout(resolve, 600));
863863+ bundleGroup.message('Creating bundle...');
864864+ await new Promise(resolve => setTimeout(resolve, 500));
865865+ bundleGroup.message('Minifying output...');
866866+ await new Promise(resolve => setTimeout(resolve, 400));
867867+ bundleGroup.success('Bundle created: dist/index.js (45kb)');
868868+869869+ // Final success
870870+ log.success('Build completed in 2.5s');
871871+872872+ outro('Ready to deploy!');
873873+}
874874+```
875875+876876+### Programmatic Cancellation with AbortController
877877+878878+Cancel prompts after a timeout or from external events:
879879+880880+```ts twoslash
881881+import { text, select, isCancel } from '@clack/prompts';
882882+883883+async function timedPrompt() {
884884+ // Auto-cancel after 30 seconds
885885+ const controller = new AbortController();
886886+ const timeoutId = setTimeout(() => controller.abort(), 30000);
887887+888888+ try {
889889+ const name = await text({
890890+ message: 'Quick! Enter your name (30 second limit):',
891891+ signal: controller.signal,
892892+ });
893893+894894+ clearTimeout(timeoutId);
895895+896896+ if (isCancel(name)) {
897897+ console.log('Time ran out or cancelled');
898898+ return;
899899+ }
900900+901901+ console.log(`Hello, ${name}!`);
902902+ } catch (error) {
903903+ console.log('Prompt was aborted');
904904+ }
905905+}
906906+907907+// Racing prompts - first response wins
908908+async function racePrompts() {
909909+ const controller = new AbortController();
910910+911911+ const result = await Promise.race([
912912+ // Option 1: Manual selection
913913+ select({
914914+ message: 'Choose a framework:',
915915+ options: [
916916+ { value: 'react', label: 'React' },
917917+ { value: 'vue', label: 'Vue' },
918918+ ],
919919+ signal: controller.signal,
920920+ }),
921921+ // Option 2: Auto-detect (simulated)
922922+ new Promise<string>(resolve => {
923923+ setTimeout(() => {
924924+ resolve('detected-framework');
925925+ controller.abort();
926926+ }, 5000);
927927+ }),
928928+ ]);
929929+930930+ console.log(`Using: ${String(result)}`);
715931}
716932```
717933
+88-10
src/content/docs/clack/packages/core.mdx
···1515- **Event system**: Comprehensive event handling for user interactions
1616- **Input validation**: Built-in support for input validation
1717- **Abort signal support**: Integration with AbortController for cancellation
1818+- **Custom I/O streams**: Support for custom input/output streams
1919+- **Configurable guide lines**: Toggle the default Clack border with `withGuide`
18201921## Installation
2022···42444345```ts twoslash
4446import {
4747+ // Prompt classes
4548 TextPrompt,
4649 SelectPrompt,
4750 ConfirmPrompt,
4851 PasswordPrompt,
4952 MultiSelectPrompt,
5053 GroupMultiSelectPrompt,
5151- SelectKeyPrompt
5454+ SelectKeyPrompt,
5555+ AutocompletePrompt,
5656+5757+ // Utilities
5858+ isCancel,
5959+ updateSettings,
6060+ settings,
6161+6262+ // Types
6363+ type ClackSettings,
6464+ type PromptOptions,
6565+ type ConfirmOptions,
6666+ type PasswordOptions,
6767+ type MultiSelectOptions,
6868+ type GroupMultiSelectOptions,
6969+ type SelectKeyOptions,
7070+ type AutocompleteOptions,
5271} from '@clack/core';
5372```
7373+7474+The package exports both the prompt classes and their corresponding options types (e.g., `ConfirmOptions`, `PasswordOptions`) for TypeScript users.
54755576## Package Structure
5677···74957596#### Key Methods
76977777-```ts twoslash
7878-9898+```ts
7999interface Prompt {
80100 // Event handling
81101 on<T extends string>(event: T, cb: (value: any) => void): void;
···93113The `Prompt` interface provides several events that can be handled:
9411495115```ts twoslash
9696-// @errors: 2304 7006 2353 2345 2459
116116+// @errors: 2304 2314 2345 7006 2353 2459
117117+// @noErrors
97118import { Prompt } from '@clack/core';
9811999120// Example usage
···123144 - Supports validation
124145 - Placeholder text
125146 - Initial value
147147+ - Separate `userInput` and `value` tracking
1261481271492. **SelectPrompt**: For selection from options
128128- - Multiple selection support
129150 - Custom rendering
130151 - Option filtering
152152+ - Disabled options support
153153+ - Text wrapping support
1311541321553. **ConfirmPrompt**: For yes/no confirmations
133156 - Yes/No shortcuts
···1361594. **AutocompletePrompt**: For searchable selection
137160 - Type-ahead filtering
138161 - Custom filtering logic
139139- - Multiple selection support
162162+ - Multiple selection support (`multiple: true`)
163163+ - Dynamic options (function or array)
1401641411655. **PasswordPrompt**: For secure input
142166 - Character masking
143167 - Validation support
168168+ - `clearOnError` option to reset on validation failure
1441691451706. **MultiSelectPrompt**: For multiple selections
146171 - Checkbox interface
147172 - Selection limits
148173 - Custom rendering
174174+ - Disabled options support
175175+ - Invert selection support
1491761501777. **GroupMultiSelectPrompt**: For grouped selections
151178 - Hierarchical options
152152- - Group selection
179179+ - Group selection (`selectableGroups` option)
153180 - Custom rendering
181181+ - Group spacing control
1541821551838. **SelectKeyPrompt**: For key-based selection
156184 - Custom key bindings
157185 - Multiple selection support
158186187187+## Global Settings
188188+189189+The core package provides global settings that affect all prompts:
190190+191191+```ts twoslash
192192+import { updateSettings, settings } from '@clack/core';
193193+194194+// Update global settings
195195+updateSettings({
196196+ // Custom key aliases for navigation
197197+ aliases: {
198198+ w: 'up',
199199+ a: 'left',
200200+ s: 'down',
201201+ d: 'right',
202202+ },
203203+ // Disable guide lines globally
204204+ withGuide: false,
205205+ // Custom messages for i18n
206206+ messages: {
207207+ cancel: 'Operación cancelada',
208208+ error: 'Se produjo un error',
209209+ },
210210+});
211211+212212+// Access current settings
213213+console.log(settings.messages.cancel);
214214+```
215215+216216+### Key Aliases
217217+218218+Default keybindings include Vim-style navigation:
219219+220220+| Key | Action |
221221+|-----|--------|
222222+| `k` | up |
223223+| `j` | down |
224224+| `h` | left |
225225+| `l` | right |
226226+| `Escape` | cancel |
227227+228228+You can add custom aliases but cannot disable the default keybindings.
229229+230230+### Guide Lines
231231+232232+The `withGuide` setting controls the display of Clack's signature border lines. Set to `false` to render prompts without the decorative guide.
233233+159234## Creating Custom Prompts
160235161236You can create custom prompts by extending the base `Prompt` class:
162237163238```ts twoslash
239239+// @errors: 2339
164240import { Prompt } from '@clack/core';
165241166242// Example of extending the base Prompt class
167167-class CustomPrompt extends Prompt {
243243+class CustomPrompt extends Prompt<string> {
168244 constructor(options: { message: string }) {
169245 super({
170246 ...options,
171171- render: () => `${options.message}\n${this.value || ''}`
247247+ render() {
248248+ return `${options.message}\n${this.value ?? ''}`;
249249+ }
172250 });
173251 }
174252}
175253```
176254177177-For detailed examples and usage patterns, check out our [examples guide](/docs/guides/examples).255255+For detailed examples and usage patterns, check out our [examples guide](/docs/clack/guides/examples).
+284-38
src/content/docs/clack/packages/prompts.mdx
···22title: Prompts
33description: Learn about the prompts package and its capabilities
44---
55-import { Aside } from "@astrojs/starlight/components"
66-import { Tabs, TabItem } from '@astrojs/starlight/components';
55+import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
7687The `@clack/prompts` package provides a collection of pre-built, high-level prompts that make it easy to create interactive command-line interfaces. It builds on top of the core package to provide a more developer-friendly experience.
98···1312- **Consistent styling**: Unified look and feel across all prompts
1413- **Type-safe**: Full TypeScript support
1514- **Customizable**: Easy to extend and modify
1515+- **AbortController support**: Cancel prompts programmatically
1616+- **Custom I/O streams**: Use custom input/output streams
16171718## Installation
1819···39404041The prompts package is designed to be intuitive and easy to use. Each prompt function returns a Promise that resolves to the user's input.
41424242-For more detailed examples and advanced usage patterns, check out our [examples guide](/docs/guides/examples) and [best practices](/docs/guides/best-practices).
4343+For more detailed examples and advanced usage patterns, check out our [examples guide](/docs/clack/guides/examples) and [best practices](/docs/clack/guides/best-practices).
4444+4545+## Common Options
4646+4747+All prompts share these common options:
4848+4949+### Guide Lines
5050+5151+The `withGuide` option controls whether the default Clack border/guide lines are displayed. You can disable them globally or per-prompt:
5252+5353+```ts twoslash
5454+import { text, updateSettings } from '@clack/prompts';
5555+5656+// Disable globally
5757+updateSettings({ withGuide: false });
5858+5959+// Or per-prompt
6060+const name = await text({
6161+ message: 'What is your name?',
6262+ withGuide: false, // Disable guide lines for this prompt
6363+});
6464+```
6565+6666+### AbortController Support
6767+6868+All prompts accept a `signal` option for programmatic cancellation:
6969+7070+```ts twoslash
7171+import { confirm } from '@clack/prompts';
7272+7373+// Auto-cancel after 10 seconds
7474+const shouldContinue = await confirm({
7575+ message: 'This will self-destruct in 10 seconds',
7676+ signal: AbortSignal.timeout(10000),
7777+});
7878+```
7979+8080+### Custom I/O Streams
8181+8282+You can provide custom input and output streams for all prompts:
8383+8484+```ts twoslash
8585+import { Writable, Readable } from 'node:stream';
8686+import { text } from '@clack/prompts';
8787+8888+declare const customInput: Readable;
8989+declare const customOutput: Writable;
9090+9191+const name = await text({
9292+ message: 'What is your name?',
9393+ input: customInput,
9494+ output: customOutput,
9595+});
9696+```
43974498## Available Prompts
459946100### Text Input
4710148102```ts twoslash
4949-// @errors: 2339
103103+// @errors: 2339 18048
50104import { text } from '@clack/prompts';
5110552106const name = await text({
53107 message: 'What is your name?',
54108 placeholder: 'John Doe',
55109 validate: (value) => {
5656- if (value.length < 2) return 'Name must be at least 2 characters';
110110+ if (!value || value.length < 2) return 'Name must be at least 2 characters';
57111 return undefined;
58112 },
59113});
···72126const secret = await password({
73127 message: 'What is your password?',
74128 mask: '*',
129129+ clearOnError: true, // Clear input when validation fails
75130 validate: (value) => {
7676- if (value.length < 8) return 'Your password must be at least 8 characters';
131131+ if (!value || value.length < 8) return 'Your password must be at least 8 characters';
77132 if (!/[A-Z]/.test(value)) return 'Your password must be least contain 1 uppercase letter';
78133 if (!/[0-9]/.test(value)) return 'Your password must be least contain 1 number';
79134 if (!/[*?!@&]/.test(value)) return 'Your password must be least contain 1 special characters (*?!@&)';
···87142<font color="#06989A">│</font> *****<span style="background-color:#FFFFFF"><font color="#FFFFFF">_</font></span>
88143<font color="#06989A">└</font></pre>
89144145145+Options:
146146+- `message`: The prompt message to display
147147+- `mask`: Character used to mask input (default: `'▪'`)
148148+- `clearOnError`: When `true`, clears the input field when validation fails (useful for security)
149149+90150### Selection
9115192152#### Simple value
···134194<font color="#555753">│</font> ○ SvelteKit (Compile-time framework)
135195<font color="#555753">└</font></pre>
136196197197+#### Disabled options
198198+199199+You can disable specific options to prevent selection:
200200+201201+```ts twoslash
202202+import { select } from '@clack/prompts';
203203+204204+const database = await select({
205205+ message: 'Select a database',
206206+ options: [
207207+ { value: 'postgres', label: 'PostgreSQL', hint: 'Recommended' },
208208+ { value: 'mysql', label: 'MySQL' },
209209+ { value: 'mongodb', label: 'MongoDB', disabled: true, hint: 'Coming soon' },
210210+ { value: 'sqlite', label: 'SQLite' },
211211+ ],
212212+});
213213+```
214214+215215+<pre class="cli-preview"><font color="#555753">│</font>
216216+<font color="#06989A">◆</font> Select a database
217217+<font color="#06989A">│</font> <font color="#4E9A06">●</font> PostgreSQL (Recommended)
218218+<font color="#06989A">│</font> ○ MySQL
219219+<font color="#06989A">│</font> <font color="#555753">○ <s>MongoDB</s> (Coming soon)</font>
220220+<font color="#06989A">│</font> ○ SQLite
221221+<font color="#06989A">└</font></pre>
222222+223223+Disabled options are displayed with strikethrough styling and cannot be selected.
224224+137225#### Multiple values
138226139227```ts twoslash
···215303<font color="#06989A">│</font> ◻ Nuxt (Vue framework)
216304<font color="#06989A">└</font></pre>
217305306306+### Path Selection
307307+308308+The `path` prompt provides an autocomplete-based file and directory path selection. It automatically suggests files and directories as the user types.
309309+310310+```ts twoslash
311311+import { path } from '@clack/prompts';
312312+313313+const selectedPath = await path({
314314+ message: 'Select a file:',
315315+ root: process.cwd(), // Starting directory
316316+ directory: false, // Set to true to only show directories
317317+});
318318+```
319319+320320+<pre class="cli-preview"><font color="#555753">│</font>
321321+<font color="#06989A">◆</font> Select a file:
322322+<font color="#06989A">│</font> Search: <span style="background-color:#FFFFFF"><font color="#181818">/</font></span>Users/project/
323323+<font color="#06989A">│</font> (3 matches)
324324+<font color="#06989A">│</font> ● /Users/project/src
325325+<font color="#06989A">│</font> ○ /Users/project/package.json
326326+<font color="#06989A">│</font> ○ /Users/project/tsconfig.json
327327+<font color="#06989A">└</font></pre>
328328+329329+Options:
330330+- `message`: The prompt message to display
331331+- `root`: The starting directory for path suggestions (defaults to current working directory)
332332+- `directory`: When `true`, only directories are shown (defaults to `false`)
333333+- `initialValue`: Pre-fill the path input with an initial value
334334+- `validate`: Custom validation function for the selected path
335335+218336### Confirmation
219337220338```ts twoslash
···305423 email: () => text({
306424 message: 'What is your email address?',
307425 validate: (value) => {
308308- if (!/^[a-z0-9_.-]+@[a-z0-9_.-]+\.[a-z]{2,}$/i.test(value)) return 'Please enter a valid email'
426426+ if (!value || !/^[a-z0-9_.-]+@[a-z0-9_.-]+\.[a-z]{2,}$/i.test(value)) return 'Please enter a valid email'
309427 }
310428 }),
311429 username: ({results}) => text({
···313431 placeholder: results.email?.replace(/@.+$/, '').toLowerCase() ?? '',
314432 validate: (value) => {
315433 // FOR DEMO PURPOSES ONLY! Use a robust validation library in production
316316- if (value.length < 2) return 'Please enter at least 2 characters'
434434+ if (!value || value.length < 2) return 'Please enter at least 2 characters'
317435 }
318436 }),
319437 password: () => password({
···440558<pre class="cli-preview"><font color="#555753">│</font>
441559<font color="#AD7FA8">◒</font> Loading{`...`}</pre>
442560443443-You can customize the spinner's messages either per instance or globally:
561561+#### Spinner Methods
562562+563563+The spinner provides multiple methods to indicate different completion states:
564564+565565+```ts twoslash
566566+import { spinner } from '@clack/prompts';
567567+568568+const spin = spinner();
569569+spin.start('Processing');
570570+571571+// Success - shows green checkmark
572572+spin.stop('Completed successfully');
573573+574574+// Or cancel - shows red square
575575+// spin.cancel('Operation cancelled');
576576+577577+// Or error - shows yellow triangle
578578+// spin.error('An error occurred');
579579+580580+// Or clear - stops without showing any message
581581+// spin.clear();
582582+```
583583+584584+You can also check if the spinner was cancelled via SIGINT (Ctrl+C):
585585+586586+```ts twoslash
587587+import { spinner } from '@clack/prompts';
588588+589589+const spin = spinner({
590590+ onCancel: () => {
591591+ console.log('User pressed Ctrl+C');
592592+ }
593593+});
594594+595595+spin.start('Long running task');
596596+// ... after some work
597597+if (spin.isCancelled) {
598598+ // Handle cancellation
599599+}
600600+```
601601+602602+#### Customization Options
444603445604```ts twoslash
446605import { spinner, updateSettings } from '@clack/prompts';
447606448448-// Global customization
607607+// Global customization for i18n
449608updateSettings({
450609 messages: {
451610 cancel: "Operation cancelled",
···455614456615// Per-instance customization
457616const spin = spinner({
617617+ indicator: 'timer', // 'dots' (default) or 'timer' for elapsed time display
458618 cancelMessage: "Process cancelled",
459619 errorMessage: "Process failed",
620620+ frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], // Custom animation frames
621621+ delay: 80, // Animation delay in ms
622622+ styleFrame: (frame) => `\x1b[35m${frame}\x1b[0m`, // Custom frame styling
460623});
461624462625spin.start('Loading');
···464627spin.stop('Done');
465628```
466629467467-The second parameter of `spinner.stop` (`code`) allow you to indicate the ending status of the spinner:
468468-- `0` (or no value) indicate a success and the symbol for a finished task will be used
469469-- `1` indicate a cancellation and the red square symbol will be used
470470-- Any other code indicate an error and the yellow triangle symbol will be used
630630+The `indicator` option supports two modes:
631631+- `'dots'`: Animated dots that cycle (default)
632632+- `'timer'`: Shows elapsed time like `[5s]` or `[1m 30s]`
471633472472-You can also use custom frames for the spinner:
634634+### Progress
635635+636636+The `progress` function displays a progress bar for long-running operations with multiple visual styles.
473637474638```ts twoslash
475475-import { spinner } from '@clack/prompts';
639639+import { progress } from '@clack/prompts';
476640477477-const spin = spinner({
478478- frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
641641+const prog = progress({
642642+ style: 'heavy', // 'light', 'heavy', or 'block'
643643+ max: 100, // Maximum value (default: 100)
644644+ size: 40, // Width of the progress bar (default: 40)
479645});
480480-spin.start('Loading');
481481-// Do something
482482-spin.stop('Done');
646646+647647+prog.start('Processing files');
648648+649649+// Advance the progress bar
650650+prog.advance(10); // Advance by 10 steps
651651+prog.advance(25, 'Processing images...'); // Advance with a message update
652652+653653+// Update just the message
654654+prog.message('Almost done...');
655655+656656+// Complete the progress
657657+prog.stop('All files processed');
483658```
484659485485-### Progress
660660+<pre class="cli-preview"><font color="#555753">│</font>
661661+<font color="#AD7FA8">━━━━━━━━━━━━━━━━━━━━</font><font color="#555753">━━━━━━━━━━━━━━━━━━━━</font> Processing images...</pre>
486662487487-The `progress` prompt displays a progress bar for long-running operations.
663663+The progress bar supports three visual styles:
664664+- `'light'`: Uses thin lines (`─`)
665665+- `'heavy'`: Uses thick lines (`━`) - default
666666+- `'block'`: Uses solid blocks (`█`)
667667+668668+Additional methods:
669669+- `advance(step?, msg?)`: Advance the progress bar by `step` (default: 1) and optionally update the message
670670+- `message(msg)`: Update the displayed message without advancing
671671+- `stop(msg?)`: Complete the progress bar with a success message
672672+- `cancel(msg?)`: Stop with a cancellation indicator
673673+- `error(msg?)`: Stop with an error indicator
674674+- `clear()`: Stop and clear the progress bar without a message
675675+676676+You can also use the timer indicator mode for time-based feedback:
488677489678```ts twoslash
490490-// @errors: 2339
491679import { progress } from '@clack/prompts';
492680493493-const prog = progress();
494494-prog.start('Processing files');
495495-// Update progress
496496-prog.message('Halfway there');
497497-// Update again
498498-prog.message('Complete');
499499-prog.stop('All files processed');
681681+const prog = progress({
682682+ indicator: 'timer', // Shows elapsed time instead of animated dots
683683+ style: 'block',
684684+ max: 50,
685685+});
686686+687687+prog.start('Downloading');
688688+// Progress shows: ████████████░░░░░░░░ Downloading [5s]
500689```
501501-502502-<pre class="cli-preview"><font color="#555753">│</font>
503503-<font color="#06989A">◆</font> Processing files
504504-<font color="#555753">│</font> [████████████████████████████████████] 100%
505505-<font color="#555753">│</font> Complete
506506-<font color="#4E9A06">◆</font> All files processed</pre>
507690508691### Note
509692···551734<font color="#555753">│</font> <font color="#555753">│</font>
552735<font color="#555753">├────────────────────────────────╯</font></pre>
553736737737+### Box
738738+739739+The `box` function renders a customizable box around text content. It's similar to `note` but offers more styling options.
740740+741741+```ts twoslash
742742+import { box } from '@clack/prompts';
743743+744744+box('This is the content of the box', 'Box Title', {
745745+ contentAlign: 'center',
746746+ titleAlign: 'center',
747747+ width: 'auto',
748748+ rounded: true,
749749+});
750750+```
751751+752752+<pre class="cli-preview"><font color="#555753">│</font> <font color="#555753">╭──────────Box Title───────────╮</font>
753753+<font color="#555753">│</font> <font color="#555753">│</font> <font color="#555753">│</font>
754754+<font color="#555753">│</font> <font color="#555753">│</font> This is the content of the <font color="#555753">│</font>
755755+<font color="#555753">│</font> <font color="#555753">│</font> box <font color="#555753">│</font>
756756+<font color="#555753">│</font> <font color="#555753">│</font> <font color="#555753">│</font>
757757+<font color="#555753">│</font> <font color="#555753">╰──────────────────────────────╯</font></pre>
758758+759759+Options:
760760+- `contentAlign`: Alignment of the content (`'left'`, `'center'`, or `'right'`)
761761+- `titleAlign`: Alignment of the title (`'left'`, `'center'`, or `'right'`)
762762+- `width`: Box width - use `'auto'` to fit content or a number for fixed width
763763+- `titlePadding`: Padding around the title
764764+- `contentPadding`: Padding around the content
765765+- `rounded`: Use rounded corners when `true` (default), square corners when `false`
766766+- `formatBorder`: Custom function to style the border characters
767767+554768### Task Log
555769556770The `taskLog` prompt provides a way to display log output that is cleared on success. This is useful for showing progress or status updates that should be removed once the task is complete.
···560774import { taskLog } from '@clack/prompts';
561775562776const log = taskLog({
563563- title: 'Installing dependencies'
777777+ title: 'Installing dependencies',
778778+ limit: 10, // Limit visible log lines (optional)
779779+ retainLog: false, // Keep full log history (optional)
564780});
565781log.message('Fetching package information...');
566782// Do some work
···574790<font color="#555753">│</font> Fetching package information...
575791<font color="#555753">│</font> Installing packages...
576792<font color="#4E9A06">◆</font> Installation complete</pre>
793793+794794+#### Task Log Groups
795795+796796+Task logs support named groups, which allow you to organize logs into separate sections with their own headers and completion states:
797797+798798+```ts twoslash
799799+// @errors: 2345
800800+import { taskLog } from '@clack/prompts';
801801+802802+const log = taskLog({
803803+ title: 'Building project'
804804+});
805805+806806+// Create a group for TypeScript compilation
807807+const tsGroup = log.group('Compiling TypeScript');
808808+tsGroup.message('Processing src/index.ts...');
809809+tsGroup.message('Processing src/utils.ts...');
810810+tsGroup.success('TypeScript compiled');
811811+812812+// Create another group for bundling
813813+const bundleGroup = log.group('Bundling');
814814+bundleGroup.message('Creating bundle...');
815815+bundleGroup.message('Minifying...');
816816+bundleGroup.success('Bundle created');
817817+818818+// Complete the overall task
819819+log.success('Build complete');
820820+```
821821+822822+Each group has its own `message()`, `success()`, and `error()` methods, allowing independent tracking of subtasks within a larger operation.
577823578824### Logs
579825