New

AnimatedCounter

Number counter that animates from 0 to target value using requestAnimationFrame with easing.

Creative & Uniquecounteranimationnumberstatsmotion

Dependencies

shadcn/ui components needed:

npx shadcn@latest add lucide-react

How to use this component

Copy the code below into your project. Make sure you have the required shadcn/ui dependencies installed. Then import and use the component in your pages or layouts.

Code

1"use client";
2
3import { useEffect, useState, useRef } from "react";
4import { TrendingUp, Users, Star, Zap } from "lucide-react";
5
6function useCounter(target: number, duration = 2000) {
7 const [count, setCount] = useState(0);
8 const frameRef = useRef<number>(0);
9
10 useEffect(() => {
11 const start = performance.now();
12 const animate = (now: number) => {
13 const elapsed = now - start;
14 const progress = Math.min(elapsed / duration, 1);
15 const eased = 1 - Math.pow(1 - progress, 3);
16 setCount(Math.floor(eased * target));
17 if (progress < 1) frameRef.current = requestAnimationFrame(animate);
18 };
19 frameRef.current = requestAnimationFrame(animate);
20 return () => cancelAnimationFrame(frameRef.current);
21 }, [target, duration]);
22
23 return count;
24}
25
26const stats = [
27 { label: "Active Users", value: 12847, prefix: "", suffix: "", icon: Users, color: "text-violet-600 dark:text-violet-400", bg: "bg-violet-50 dark:bg-violet-900/20" },
28 { label: "Revenue", value: 94200, prefix: "$", suffix: "", icon: TrendingUp, color: "text-emerald-600 dark:text-emerald-400", bg: "bg-emerald-50 dark:bg-emerald-900/20" },
29 { label: "Rating", value: 49, prefix: "", suffix: "", icon: Star, color: "text-amber-600 dark:text-amber-400", bg: "bg-amber-50 dark:bg-amber-900/20" },
30 { label: "Uptime", value: 999, prefix: "", suffix: "%", icon: Zap, color: "text-blue-600 dark:text-blue-400", bg: "bg-blue-50 dark:bg-blue-900/20" },
31];
32
33export default function AnimatedCounter() {
34 return (
35 <div className="mx-auto grid w-full max-w-2xl grid-cols-2 gap-4 md:grid-cols-4">
36 {stats.map((stat) => {
37 const count = useCounter(stat.value, 2500);
38 const display = stat.label === "Rating" ? (count / 10).toFixed(1) : stat.label === "Uptime" ? (count / 10).toFixed(1) : count.toLocaleString();
39 return (
40 <div key={stat.label} className="rounded-2xl border border-zinc-200 bg-white p-5 text-center shadow-sm dark:border-zinc-800 dark:bg-zinc-950">
41 <div className={`mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-xl ${stat.bg}`}>
42 <stat.icon className={`h-5 w-5 ${stat.color}`} />
43 </div>
44 <p className={`text-2xl font-bold tabular-nums ${stat.color}`}>{stat.prefix}{display}{stat.suffix}</p>
45 <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">{stat.label}</p>
46 </div>
47 );
48 })}
49 </div>
50 );
51}

Related Creative & Unique Components

Command Palette

Search for a command to run...