Components
Interactive Progress Bar
Interactive Progress Bar
Multi-stage progress bars with animated particles, completion effects, and real-time status updates.
Upload Progress0%
Processing0%
Verification0%
Processing...
Installation
1
Install the packages
npm i motion clsx tailwind-merge2
Add util file
lib/util.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3
Copy and paste the following code into your project
interactive-progress-bar.tsx
"use client";
import { motion } from "motion/react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
type InteractiveProgressBarProps = {
progress?: number;
duration?: number;
};
const InteractiveProgressBar = ({
progress = 0,
duration = 2000,
}: InteractiveProgressBarProps) => {
const [animatedProgress, setAnimatedProgress] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setAnimatedProgress(progress);
}, 100);
return () => clearTimeout(timer);
}, [progress]);
return (
<div className="w-full max-w-md mx-auto">
<div className="relative h-4 bg-neutral-800 rounded-full overflow-hidden">
{/* Background track */}
<div className="absolute inset-0 bg-gradient-to-r from-neutral-700 to-neutral-600 rounded-full" />
{/* Progress fill */}
<motion.div
className="absolute left-0 top-0 h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${animatedProgress}%` }}
transition={{ duration: duration / 1000, ease: "easeInOut" }}
/>
{/* Animated particles */}
<motion.div
className="absolute top-0 h-full w-2 bg-white rounded-full opacity-80"
initial={{ left: 0, opacity: 0 }}
animate={{
left: `${animatedProgress}%`,
opacity: [0, 1, 0],
}}
transition={{
duration: duration / 1000,
ease: "easeInOut",
opacity: { duration: 0.3 },
}}
/>
{/* Completion effect */}
{animatedProgress === 100 && (
<motion.div
className="absolute inset-0 bg-gradient-to-r from-green-400 to-blue-500 rounded-full"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, ease: "easeOut" }}
/>
)}
</div>
{/* Progress text */}
<div className="mt-2 text-center">
<motion.span
className="text-sm font-medium text-white"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
{Math.round(animatedProgress)}% Complete
</motion.span>
</div>
</div>
);
};
export default InteractiveProgressBar;"use client";
import { motion } from "motion/react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
type InteractiveProgressBarProps = {
progress?: number;
duration?: number;
};
const InteractiveProgressBar = ({
progress = 0,
duration = 2000,
}: InteractiveProgressBarProps) => {
const [animatedProgress, setAnimatedProgress] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setAnimatedProgress(progress);
}, 100);
return () => clearTimeout(timer);
}, [progress]);
return (
<div className="w-full max-w-md mx-auto">
<div className="relative h-4 bg-neutral-800 rounded-full overflow-hidden">
{/* Background track */}
<div className="absolute inset-0 bg-gradient-to-r from-neutral-700 to-neutral-600 rounded-full" />
{/* Progress fill */}
<motion.div
className="absolute left-0 top-0 h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${animatedProgress}%` }}
transition={{ duration: duration / 1000, ease: "easeInOut" }}
/>
{/* Animated particles */}
<motion.div
className="absolute top-0 h-full w-2 bg-white rounded-full opacity-80"
initial={{ left: 0, opacity: 0 }}
animate={{
left: `${animatedProgress}%`,
opacity: [0, 1, 0],
}}
transition={{
duration: duration / 1000,
ease: "easeInOut",
opacity: { duration: 0.3 },
}}
/>
{/* Completion effect */}
{animatedProgress === 100 && (
<motion.div
className="absolute inset-0 bg-gradient-to-r from-green-400 to-blue-500 rounded-full"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, ease: "easeOut" }}
/>
)}
</div>
{/* Progress text */}
<div className="mt-2 text-center">
<motion.span
className="text-sm font-medium text-white"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
{Math.round(animatedProgress)}% Complete
</motion.span>
</div>
</div>
);
};
export default InteractiveProgressBar;4
Update the import paths to match your project setup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| progress | number | 0 | The current progress value (0-100). |
| duration | number | 2000 | Animation duration in milliseconds. |