Progress Component ๐Ÿ“Š

August 8, 2025 ยท View on GitHub

Beautiful, accessible progress indicators with Material Design animations and comprehensive customization options. Perfect for loading states, file uploads, and tracking task completion.

Features

  • ๐ŸŽฏ 12 Variants - Default, Primary, Secondary, Success, Warning, Destructive, Info, Gradient variants
  • ๐Ÿ“ 6 Sizes - Extra Small to 2X Large with responsive design
  • โšก Indeterminate Animation - Smooth Material Design loading animation with CSS
  • ๐Ÿ“ฑ Responsive - Mobile-first design that works on all screen sizes
  • โ™ฟ Accessibility - WCAG compliant with ARIA live regions and screen reader announcements
  • ๐ŸŽจ Customizable - Multiple text positions, custom formatters, additional info display
  • ๐Ÿ”ง TypeScript - Full type safety with comprehensive interfaces
  • โœจ Animations - Shimmer, pulse, bounce effects and smooth transitions

Installation

Initialize Angular SuperUI in your project:

ngsui-cli init

Add the Progress component:

ngsui-cli add progress

Usage

Import the Progress component in your Angular component:

import { Component } from '@angular/core';
import { ProgressComponent } from 'angular-superui';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [ProgressComponent],
  template: `
    <ProgressComponent [value]="50" [max]="100" />
  `
})
export class ExampleComponent {}

Examples

Basic Progress

@Component({
  template: `
    <ProgressComponent [value]="73" [max]="100" [showText]="true" />
  `
})

Indeterminate Loading

@Component({
  template: `
    <ProgressComponent 
      [indeterminate]="true" 
      variant="primary" 
      [showText]="true" 
      size="lg"
    />
  `
})

Color Variants

@Component({
  template: `
    <!-- Primary -->
    <ProgressComponent [value]="65" variant="primary" [showText]="true" />
    
    <!-- Success -->
    <ProgressComponent [value]="85" variant="success" [showText]="true" />
    
    <!-- Warning -->
    <ProgressComponent [value]="45" variant="warning" [showText]="true" />
    
    <!-- Destructive -->
    <ProgressComponent [value]="25" variant="destructive" [showText]="true" />
    
    <!-- Info -->
    <ProgressComponent [value]="60" variant="info" [showText]="true" />
  `
})

Gradient Variants

@Component({
  template: `
    <!-- Primary Gradient with Glow -->
    <ProgressComponent
      [value]="70"
      variant="gradient"
      [showText]="true"
      [glow]="true"
      size="lg"
    />
    
    <!-- Success Gradient with Animation -->
    <ProgressComponent
      [value]="85"
      variant="gradient-success"
      [showText]="true"
      animated="shimmer"
      size="lg"
    />
    
    <!-- Warning Gradient with Stripes -->
    <ProgressComponent
      [value]="55"
      variant="gradient-warning"
      [showText]="true"
      [striped]="true"
      size="lg"
    />
    
    <!-- Danger Gradient with Pulse -->
    <ProgressComponent
      [value]="30"
      variant="gradient-danger"
      [showText]="true"
      animated="pulse"
      size="lg"
    />
  `
})

Size Variations

@Component({
  template: `
    <!-- Extra Small -->
    <ProgressComponent [value]="60" size="xs" variant="primary" />
    
    <!-- Small -->
    <ProgressComponent [value]="70" size="sm" variant="primary" />
    
    <!-- Default -->
    <ProgressComponent [value]="80" size="default" variant="primary" [showText]="true" />
    
    <!-- Large -->
    <ProgressComponent [value]="90" size="lg" variant="primary" [showText]="true" />
    
    <!-- Extra Large with inside text -->
    <ProgressComponent
      [value]="95"
      size="xl"
      variant="gradient"
      [showText]="true"
      textPosition="inside"
    />
    
    <!-- 2X Large with maximum impact -->
    <ProgressComponent
      [value]="100"
      size="2xl"
      variant="gradient-success"
      [showText]="true"
      textPosition="inside"
      [glow]="true"
    />
  `
})

Text Positioning

@Component({
  template: `
    <!-- Text on top -->
    <ProgressComponent
      [value]="60"
      [showText]="true"
      textPosition="top"
      textAlign="left"
    />
    
    <!-- Text on bottom (default) -->
    <ProgressComponent
      [value]="75"
      [showText]="true"
      textPosition="bottom"
      textAlign="center"
    />
    
    <!-- Text inside progress bar -->
    <ProgressComponent
      [value]="85"
      [showText]="true"
      textPosition="inside"
      size="xl"
    />
    
    <!-- Overlay text -->
    <ProgressComponent
      [value]="90"
      [showText]="true"
      textPosition="overlay"
      size="lg"
    />
  `
})

Custom Text Formatting

@Component({
  template: `
    <!-- Custom static text -->
    <ProgressComponent
      [value]="75"
      [showText]="true"
      [customText]="'Processing: 75 files'"
    />
    
    <!-- Custom formatter function -->
    <ProgressComponent
      [value]="uploadProgress"
      [showText]="true"
      [textFormat]="uploadTextFormatter"
    />
  `
})
export class CustomTextExample {
  uploadProgress = 65;
  
  uploadTextFormatter = (value: number, max: number): string => {
    const percentage = Math.round((value / max) * 100);
    const mbUploaded = Math.round((value / max) * 150); // Simulate 150MB file
    return `${mbUploaded} MB / 150 MB (${percentage}%)`;
  };
}

Additional Information Display

@Component({
  template: `
    <ProgressComponent
      [value]="progress"
      [showText]="true"
      [showAdditionalInfo]="true"
      [additionalInfoLeft]="progressStatus"
      [additionalInfoRight]="timeRemaining"
      size="lg"
      variant="gradient"
    />
  `
})
export class AdditionalInfoExample {
  progress = 65;
  progressStatus = 'In progress';
  timeRemaining = '2 min remaining';
}

Animation Effects

@Component({
  template: `
    <!-- Shimmer animation -->
    <ProgressComponent
      [value]="70"
      animated="shimmer"
      variant="gradient"
      [showText]="true"
    />
    
    <!-- Pulse animation -->
    <ProgressComponent
      [value]="80"
      animated="pulse"
      variant="success"
      [showText]="true"
    />
    
    <!-- Bounce animation -->
    <ProgressComponent
      [value]="90"
      animated="bounce"
      variant="primary"
      [showText]="true"
    />
    
    <!-- Striped with animation -->
    <ProgressComponent
      [value]="75"
      [striped]="true"
      variant="warning"
      [showText]="true"
    />
  `
})

Special Features

@Component({
  template: `
    <!-- Reversed direction -->
    <ProgressComponent
      [value]="60"
      variant="warning"
      [reversed]="true"
      [showText]="true"
      size="lg"
    />
    
    <!-- Custom value range -->
    <ProgressComponent
      [value]="50"
      [min]="20"
      [max]="80"
      variant="info"
      [showText]="true"
      size="lg"
    />
    
    <!-- Glow effect -->
    <ProgressComponent
      [value]="85"
      variant="gradient"
      [glow]="true"
      [showText]="true"
      size="lg"
    />
  `
})

File Upload Simulation

@Component({
  selector: 'app-upload-example',
  template: `
    <div class="space-y-4">
      <div class="flex gap-3">
        <button 
          (click)="startUpload()"
          [disabled]="isUploading || uploadProgress > 0"
          class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
        >
          Start Upload
        </button>
        <button 
          (click)="pauseUpload()"
          [disabled]="!isUploading || uploadPaused || uploadProgress >= 100"
          class="px-4 py-2 bg-yellow-600 text-white rounded hover:bg-yellow-700 disabled:opacity-50"
        >
          Pause
        </button>
        <button 
          (click)="cancelUpload()"
          [disabled]="!isUploading && uploadProgress === 0"
          class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50"
        >
          Cancel
        </button>
      </div>

      <ProgressComponent
        [value]="uploadProgress"
        [max]="100"
        [variant]="uploadVariant"
        [showText]="true"
        [textFormat]="uploadTextFormatter"
        [showAdditionalInfo]="true"
        [additionalInfoLeft]="uploadSpeed"
        [additionalInfoRight]="uploadTimeRemaining"
        size="lg"
        [animated]="isUploading && !uploadPaused ? 'shimmer' : false"
      />
      
      <div class="text-sm text-gray-600">
        Status: {{ uploadStatus }}
      </div>
    </div>
  `
})
export class UploadExample {
  uploadProgress = 0;
  isUploading = false;
  uploadPaused = false;
  uploadSpeed = '';
  uploadTimeRemaining = '';
  uploadStatus = '';
  uploadVariant: 'primary' | 'success' | 'warning' | 'default' = 'default';
  private uploadInterval?: number;

  uploadTextFormatter = (value: number, max: number): string => {
    const percentage = Math.round((value / max) * 100);
    if (percentage === 100) return 'Upload Complete!';
    if (this.uploadPaused) return `Paused at ${percentage}%`;
    const mbUploaded = Math.round((value / max) * 150);
    return `${mbUploaded} MB / 150 MB (${percentage}%)`;
  };

  startUpload(): void {
    this.isUploading = true;
    this.uploadPaused = false;
    this.uploadProgress = 0;
    this.uploadVariant = 'primary';
    this.uploadStatus = 'Starting upload...';

    this.uploadInterval = window.setInterval(() => {
      if (!this.uploadPaused) {
        const current = this.uploadProgress;
        if (current >= 100) {
          this.completeUpload();
        } else {
          const increment = Math.random() * 3 + 1;
          this.uploadProgress = Math.min(current + increment, 100);
          
          const speed = (Math.random() * 5 + 2).toFixed(1);
          this.uploadSpeed = `${speed} MB/s`;
          
          const remaining = Math.ceil((100 - this.uploadProgress) / 2);
          this.uploadTimeRemaining = `${remaining}s remaining`;
          
          this.uploadStatus = `Uploading... ${Math.round(this.uploadProgress)}%`;
        }
      }
    }, 300);
  }

  pauseUpload(): void {
    this.uploadPaused = true;
    this.uploadVariant = 'warning';
    this.uploadStatus = 'Upload paused';
    this.uploadSpeed = '';
    this.uploadTimeRemaining = 'Paused';
  }

  cancelUpload(): void {
    this.isUploading = false;
    this.uploadPaused = false;
    this.uploadProgress = 0;
    this.uploadVariant = 'default';
    this.uploadStatus = 'Upload cancelled';
    this.uploadSpeed = '';
    this.uploadTimeRemaining = '';
    if (this.uploadInterval) {
      clearInterval(this.uploadInterval);
    }
  }

  private completeUpload(): void {
    this.isUploading = false;
    this.uploadVariant = 'success';
    this.uploadStatus = 'Upload completed successfully!';
    this.uploadSpeed = '';
    this.uploadTimeRemaining = 'Complete';
    if (this.uploadInterval) {
      clearInterval(this.uploadInterval);
    }
  }
}

Multi-Step Process

@Component({
  selector: 'app-multi-step-example',
  template: `
    <div class="space-y-6">
      <button 
        (click)="startProcess()"
        [disabled]="isProcessing || overallProgress > 0"
        class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50"
      >
        Start Process
      </button>

      <div class="space-y-3">
        <div *ngFor="let step of steps; track step.id" class="space-y-2">
          <div class="flex justify-between text-sm">
            <span [class]="step.completed ? 'text-green-600 font-medium' : step.active ? 'text-blue-600 font-medium' : 'text-gray-500'">
              {{ step.name }}
            </span>
            <span *ngIf="step.completed" class="text-green-600">โœ“</span>
          </div>
          <ProgressComponent
            [value]="step.progress"
            [max]="100"
            [variant]="step.completed ? 'success' : step.active ? 'primary' : 'default'"
            size="sm"
            [animated]="step.active ? 'shimmer' : false"
          />
        </div>
      </div>

      <ProgressComponent
        [value]="overallProgress"
        [max]="100"
        variant="gradient-success"
        [showText]="true"
        size="lg"
        [customText]="'Overall Progress: ' + Math.round(overallProgress) + '%'"
      />
    </div>
  `
})
export class MultiStepExample {
  Math = Math;
  isProcessing = false;
  
  steps = [
    { id: 1, name: 'Loading configuration', progress: 0, active: false, completed: false },
    { id: 2, name: 'Connecting to database', progress: 0, active: false, completed: false },
    { id: 3, name: 'Initializing services', progress: 0, active: false, completed: false },
    { id: 4, name: 'Loading user preferences', progress: 0, active: false, completed: false },
    { id: 5, name: 'Finalizing setup', progress: 0, active: false, completed: false },
  ];

  get overallProgress(): number {
    const totalSteps = this.steps.length;
    const completedSteps = this.steps.filter(step => step.completed).length;
    const activeStep = this.steps.find(step => step.active);

    if (activeStep) {
      return ((completedSteps * 100) + activeStep.progress) / totalSteps;
    }
    return (completedSteps / totalSteps) * 100;
  }

  startProcess(): void {
    this.isProcessing = true;
    this.steps.forEach(step => {
      step.progress = 0;
      step.active = false;
      step.completed = false;
    });
    this.processNextStep(0);
  }

  private processNextStep(stepIndex: number): void {
    if (stepIndex >= this.steps.length) {
      this.isProcessing = false;
      return;
    }

    const currentStep = this.steps[stepIndex];
    currentStep.active = true;

    const interval = setInterval(() => {
      if (currentStep.progress >= 100) {
        currentStep.progress = 100;
        currentStep.active = false;
        currentStep.completed = true;
        clearInterval(interval);
        setTimeout(() => this.processNextStep(stepIndex + 1), 500);
      } else {
        currentStep.progress += Math.random() * 15 + 5;
        currentStep.progress = Math.min(currentStep.progress, 100);
      }
    }, 200);
  }
}

Accessibility Features

@Component({
  template: `
    <!-- Basic accessibility -->
    <ProgressComponent
      [value]="progress"
      [ariaLabel]="'File upload progress'"
      [showText]="true"
    />
    
    <!-- Enhanced accessibility with announcements -->
    <ProgressComponent
      [value]="progress"
      [accessibility]="{
        announceChanges: true,
        announceThreshold: 25,
        liveRegionPoliteness: 'assertive',
        ariaLabel: 'System backup progress',
        ariaDescription: 'Backing up your files to cloud storage'
      }"
      [showText]="true"
    />
    
    <!-- Custom announcement format -->
    <ProgressComponent
      [value]="progress"
      [accessibility]="{
        announceChanges: true,
        announceFormat: customAnnounceFormat
      }"
      [showText]="true"
    />
  `
})
export class AccessibilityExample {
  progress = 45;
  
  customAnnounceFormat = (value: number, max: number): string => {
    const percentage = Math.round((value / max) * 100);
    return `Backup progress: ${percentage} percent complete`;
  };
}

API Reference

Props

PropTypeDefaultDescription
valuenumber0Current progress value
maxnumber100Maximum progress value
minnumber0Minimum progress value
size'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl''default'Size of the progress bar
variantProgressIndicatorVariant['variant']'default'Visual style variant
containerVariantProgressVariant['variant']'default'Container background variant
animated'shimmer' | 'pulse' | 'bounce' | falsefalseAnimation effect
stripedbooleanfalseEnable striped pattern
glowbooleanfalseEnable glow effect
classNamestring''Additional CSS classes
showTextbooleanfalseShow progress text
textPosition'top' | 'bottom' | 'inside' | 'overlay''bottom'Position of progress text
textAlign'left' | 'center' | 'right''center'Text alignment
textFormat(value: number, max: number) => stringundefinedCustom text formatter function
customTextstring''Static custom text
showAdditionalInfobooleanfalseShow additional info row
additionalInfoLeftstring''Left additional info text
additionalInfoRightstring''Right additional info text
indeterminatebooleanfalseEnable indeterminate loading animation
reversedbooleanfalseReverse progress direction
accessibilityProgressAccessibility{}Accessibility configuration
ariaLabelstring''ARIA label for screen readers
animationConfigProgressAnimationConfigDefault configAnimation timing configuration

Variant Types

ProgressIndicatorVariant

type ProgressIndicatorVariant = {
  variant: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'info' | 'purple' | 'pink' | 'gradient' | 'gradient-success' | 'gradient-warning' | 'gradient-danger';
  animated: 'shimmer' | 'pulse' | 'bounce' | false;
  striped: boolean;
  glow: boolean;
}

ProgressVariant

type ProgressVariant = {
  size: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl';
  variant: 'default' | 'secondary' | 'muted';
}

Interfaces

ProgressAccessibility

interface ProgressAccessibility {
  /** Custom ARIA label */
  ariaLabel?: string;
  /** Element ID that labels this progress */
  ariaLabelledBy?: string;
  /** Element ID that describes this progress */
  ariaDescribedBy?: string;
  /** Announce progress changes to screen readers */
  announceChanges?: boolean;
  /** Minimum change threshold for announcements */
  announceThreshold?: number;
  /** Live region politeness setting */
  liveRegionPoliteness?: 'polite' | 'assertive';
  /** Atomic announcements */
  atomicAnnouncements?: boolean;
  /** Custom live region ID */
  liveRegionId?: string;
  /** Custom announcement format function */
  announceFormat?: (value: number, max: number) => string;
}

ProgressAnimationConfig

interface ProgressAnimationConfig {
  /** Enable smooth transitions */
  smooth?: boolean;
  /** Animation duration in milliseconds */
  duration?: number;
  /** CSS easing function */
  easing?: string;
  /** Enable entrance animation */
  entrance?: boolean;
  /** Enable value change animation */
  valueAnimation?: boolean;
}

Utility Functions

createProgressAccessibility

function createProgressAccessibility(
  options: Partial<ProgressAccessibility> = {}
): ProgressAccessibility

Creates a progress accessibility configuration with sensible defaults.

createAnimationConfig

function createAnimationConfig(
  options: Partial<ProgressAnimationConfig> = {}
): ProgressAnimationConfig

Creates an animation configuration with sensible defaults.

formatProgressText

function formatProgressText(
  value: number,
  max: number,
  format?: 'percentage' | 'fraction' | 'custom'
): string

Utility function for formatting progress text in different formats.

Styling

The Progress component uses Tailwind CSS classes and CSS custom properties for styling. You can customize the appearance by:

Custom Classes

@Component({
  template: `
    <ProgressComponent 
      [value]="75" 
      className="shadow-lg border border-gray-200 rounded-lg"
    />
  `
})

CSS Custom Properties

The component respects these CSS custom properties:

  • --primary / --primary-foreground
  • --secondary / --secondary-foreground
  • --success / --success-foreground
  • --warning / --warning-foreground
  • --destructive / --destructive-foreground
  • --info / --info-foreground
  • --background / --foreground
  • --muted / --muted-foreground
  • --border / --ring

Custom Animations

You can override the indeterminate animation with custom CSS:

.progress-indeterminate::before {
  animation: custom-indeterminate 2s linear infinite;
}

@keyframes custom-indeterminate {
  0% { left: -50%; right: 100%; }
  50% { left: 0%; right: 50%; }
  100% { left: 100%; right: -50%; }
}

Accessibility

The Progress component is built with accessibility in mind:

  • ARIA Compliance: Proper role="progressbar" with aria-valuenow, aria-valuemin, and aria-valuemax
  • Screen Reader Support: Live region announcements for progress changes
  • Indeterminate State: Proper handling with aria-valuenow="null" for loading states
  • Keyboard Navigation: Focus management and keyboard interaction support
  • High Contrast: Visible focus indicators and sufficient color contrast
  • Customizable Announcements: Configure announcement frequency and format

WCAG Guidelines

The component follows WCAG 2.1 guidelines:

  • Level A: Basic functionality and keyboard access
  • Level AA: Color contrast ratios and text alternatives
  • Level AAA: Enhanced accessibility features and announcements

Best Practices

  1. Use appropriate variants - Match colors to semantic meaning (success for completion, warning for issues)
  2. Provide meaningful text - Always include descriptive text for screen readers
  3. Set reasonable thresholds - Don't announce every small change to avoid overwhelming users
  4. Use indeterminate wisely - Only for truly unknown progress, not as a loading spinner
  5. Consider context - Match size and styling to your application's design system
  6. Test with assistive technology - Ensure announcements work properly with screen readers

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • iOS Safari 14+
  • Chrome Android 90+
  • Button - For triggering progress-related actions
  • Alert - For displaying progress-related messages
  • Card - For containing progress indicators
  • Badge - For showing progress status

Built with โค๏ธ using Angular 17+ and Tailwind CSS