Visual feedback components for long-running operations, including customizable spinners and progress messaging.
Animated loading spinner for long-running operations with customizable indicators and dynamic messaging.
/**
* Creates a configurable animated spinner for visual progress feedback
* @param options - Optional configuration for the spinner behavior
* @returns Spinner control object with start, stop, and message methods
*/
function spinner(options?: SpinnerOptions): {
/** Start the spinner with an optional initial message */
start: (msg?: string) => void;
/** Stop the spinner with optional completion message and exit code */
stop: (msg?: string, code?: number) => void;
/** Update the spinner message while it's running */
message: (msg?: string) => void;
};
interface SpinnerOptions {
/** Type of progress indicator to display */
indicator?: 'dots' | 'timer';
}Usage Examples:
import { spinner } from "@clack/prompts";
// Basic spinner usage
const s = spinner();
s.start("Installing dependencies");
// Simulate work
await new Promise(resolve => setTimeout(resolve, 2000));
s.stop("Dependencies installed");
// Spinner with dynamic messages
const s2 = spinner();
s2.start("Processing files");
// Update message during operation
setTimeout(() => s2.message("Reading configuration"), 500);
setTimeout(() => s2.message("Validating data"), 1000);
setTimeout(() => s2.message("Generating output"), 1500);
await new Promise(resolve => setTimeout(resolve, 2000));
s2.stop("Processing complete");
// Timer-based spinner
const s3 = spinner({ indicator: 'timer' });
s3.start("Building project");
// Long-running operation
await new Promise(resolve => setTimeout(resolve, 5000));
s3.stop("Build complete"); // Shows elapsed timeimport { spinner } from "@clack/prompts";
const s = spinner();
s.start("Deploying application");
try {
// Simulate deployment process
s.message("Uploading files");
await new Promise(resolve => setTimeout(resolve, 1000));
s.message("Running build");
await new Promise(resolve => setTimeout(resolve, 1500));
// Simulate error
throw new Error("Build failed");
} catch (error) {
// Stop with error (code 1 shows error styling)
s.stop(`Deployment failed: ${error.message}`, 1);
process.exit(1);
}
s.stop("Deployment successful");import { spinner } from "@clack/prompts";
// Multi-stage process with detailed progress
async function complexOperation() {
const s = spinner({ indicator: 'timer' });
try {
s.start("Initializing");
// Stage 1
s.message("Connecting to server");
await simulateWork(800);
s.message("Authenticating");
await simulateWork(500);
// Stage 2
s.message("Downloading files (0/10)");
for (let i = 1; i <= 10; i++) {
await simulateWork(200);
s.message(`Downloading files (${i}/10)`);
}
// Stage 3
s.message("Processing data");
await simulateWork(1000);
s.message("Generating report");
await simulateWork(600);
s.stop("Operation completed successfully");
} catch (error) {
s.stop("Operation failed", 1);
throw error;
}
}
function simulateWork(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Conditional spinner based on environment
const isCI = process.env.CI === 'true';
async function smartSpinner(task: () => Promise<void>, message: string) {
if (isCI) {
// In CI, just log without spinner
console.log(message);
await task();
console.log("✓ Complete");
} else {
// Interactive environment, use spinner
const s = spinner();
s.start(message);
try {
await task();
s.stop("Complete");
} catch (error) {
s.stop("Failed", 1);
throw error;
}
}
}Dots Indicator (default):
Timer Indicator:
[5s] or [2m 30s]const s = spinner();
// Start with initial message
s.start("Starting process");
// Update message during operation
s.message("Step 1: Preparing");
s.message("Step 2: Processing");
s.message("Step 3: Finalizing");
// Stop with completion message
s.stop("Process completed");The stop method accepts an optional exit code that affects styling:
const s = spinner();
s.start("Risky operation");
// Success
s.stop("Operation succeeded", 0);
// User cancellation
s.stop("Operation cancelled", 1);
// Error
s.stop("Operation failed", 2);Spinners automatically handle process cleanup:
Spinners are automatically used by the tasks function:
import { tasks } from "@clack/prompts";
// Each task gets its own spinner automatically
await tasks([
{
title: "Task 1",
task: async (message) => {
// Use message callback to update spinner
message("Substep 1");
await work();
message("Substep 2");
await moreWork();
return "Task 1 complete";
},
},
]);For operations not using tasks, manage spinners manually:
import { spinner, text, isCancel, cancel } from "@clack/prompts";
const projectName = await text({
message: "Project name:",
});
if (isCancel(projectName)) {
cancel("Setup cancelled");
process.exit(0);
}
// Start spinner for long operation
const s = spinner();
s.start(`Creating project: ${projectName}`);
try {
s.message("Creating directories");
await createDirectories();
s.message("Generating files");
await generateFiles();
s.message("Installing dependencies");
await installDependencies();
s.stop("Project created successfully");
} catch (error) {
s.stop(`Creation failed: ${error.message}`, 1);
process.exit(1);
}start() has a matching stop()