or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-workflows.mdcancellation-handling.mdindex.mdinteractive-prompts.mdlogging-system.mdprogress-indicators.mdselection-prompts.mdsession-management.mdsettings-configuration.md
tile.json

progress-indicators.mddocs/

Progress Indicators

Visual feedback components for long-running operations, including customizable spinners and progress messaging.

Capabilities

Spinner

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 time

Error Handling with Spinners

import { 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");

Advanced Spinner Patterns

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;
    }
  }
}

Spinner Behavior

Visual Indicators

Dots Indicator (default):

  • Animated rotating dots (◒ ◐ ◓ ◑) on Unicode terminals
  • Fallback to simple characters (• o O 0) on basic terminals
  • Displays loading dots after the message (message...)

Timer Indicator:

  • Same animated spinner
  • Shows elapsed time in format [5s] or [2m 30s]
  • Updates in real-time during operation

Message Management

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");

Exit Codes

The stop method accepts an optional exit code that affects styling:

  • 0 (default): Success styling with green checkmark
  • 1: Cancellation styling with red X
  • 2 or higher: Error styling with red X
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);

Automatic Cleanup

Spinners automatically handle process cleanup:

  • Ctrl+C (SIGINT): Gracefully stops spinner and exits
  • Process termination: Cleans up spinner state
  • Uncaught exceptions: Stops spinner before crash
  • Unhandled rejections: Stops spinner with error message

Integration with Other Components

With Tasks

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";
    },
  },
]);

With Manual Control

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);
}

Best Practices

  1. Always stop spinners - Ensure every start() has a matching stop()
  2. Use descriptive messages - Help users understand what's happening
  3. Update messages for long operations - Show progress through different stages
  4. Handle errors gracefully - Use appropriate exit codes and error messages
  5. Choose appropriate indicators - Use timer for very long operations
  6. Test in CI environments - Spinners automatically adapt to CI/non-interactive environments