Beautiful, accessible progress indicators with Material Design animations and comprehensive customization options. Perfect for loading states, file uploads, and tracking task completion.
- 🎯 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
Initialize Angular SuperUI in your project:
ngsui-cli initAdd the Progress component:
ngsui-cli add progressImport 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 {}@Component({
template: `
<ProgressComponent [value]="73" [max]="100" [showText]="true" />
`
})@Component({
template: `
<ProgressComponent
[indeterminate]="true"
variant="primary"
[showText]="true"
size="lg"
/>
`
})@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" />
`
})@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"
/>
`
})@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"
/>
`
})@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"
/>
`
})@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}%)`;
};
}@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';
}@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"
/>
`
})@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"
/>
`
})@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);
}
}
}@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);
}
}@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`;
};
}| Prop | Type | Default | Description |
|---|---|---|---|
value |
number |
0 |
Current progress value |
max |
number |
100 |
Maximum progress value |
min |
number |
0 |
Minimum progress value |
size |
'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' |
'default' |
Size of the progress bar |
variant |
ProgressIndicatorVariant['variant'] |
'default' |
Visual style variant |
containerVariant |
ProgressVariant['variant'] |
'default' |
Container background variant |
animated |
'shimmer' | 'pulse' | 'bounce' | false |
false |
Animation effect |
striped |
boolean |
false |
Enable striped pattern |
glow |
boolean |
false |
Enable glow effect |
className |
string |
'' |
Additional CSS classes |
showText |
boolean |
false |
Show progress text |
textPosition |
'top' | 'bottom' | 'inside' | 'overlay' |
'bottom' |
Position of progress text |
textAlign |
'left' | 'center' | 'right' |
'center' |
Text alignment |
textFormat |
(value: number, max: number) => string |
undefined |
Custom text formatter function |
customText |
string |
'' |
Static custom text |
showAdditionalInfo |
boolean |
false |
Show additional info row |
additionalInfoLeft |
string |
'' |
Left additional info text |
additionalInfoRight |
string |
'' |
Right additional info text |
indeterminate |
boolean |
false |
Enable indeterminate loading animation |
reversed |
boolean |
false |
Reverse progress direction |
accessibility |
ProgressAccessibility |
{} |
Accessibility configuration |
ariaLabel |
string |
'' |
ARIA label for screen readers |
animationConfig |
ProgressAnimationConfig |
Default config | Animation timing configuration |
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;
}type ProgressVariant = {
size: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl';
variant: 'default' | 'secondary' | 'muted';
}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;
}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;
}function createProgressAccessibility(
options: Partial<ProgressAccessibility> = {}
): ProgressAccessibilityCreates a progress accessibility configuration with sensible defaults.
function createAnimationConfig(
options: Partial<ProgressAnimationConfig> = {}
): ProgressAnimationConfigCreates an animation configuration with sensible defaults.
function formatProgressText(
value: number,
max: number,
format?: 'percentage' | 'fraction' | 'custom'
): stringUtility function for formatting progress text in different formats.
The Progress component uses Tailwind CSS classes and CSS custom properties for styling. You can customize the appearance by:
@Component({
template: `
<ProgressComponent
[value]="75"
className="shadow-lg border border-gray-200 rounded-lg"
/>
`
})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
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%; }
}The Progress component is built with accessibility in mind:
- ARIA Compliance: Proper
role="progressbar"witharia-valuenow,aria-valuemin, andaria-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
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
- Use appropriate variants - Match colors to semantic meaning (success for completion, warning for issues)
- Provide meaningful text - Always include descriptive text for screen readers
- Set reasonable thresholds - Don't announce every small change to avoid overwhelming users
- Use indeterminate wisely - Only for truly unknown progress, not as a loading spinner
- Consider context - Match size and styling to your application's design system
- Test with assistive technology - Ensure announcements work properly with screen readers
- 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