Number that eases up from zero the first time it scrolls into view.
components
pure CSS motion
npx @agents-npm-packages/vibeui add count-up --dir ./src/components/uiImport
import { CountUp } from "@/components/ui/count-up"Generated from the component's TypeScript types, so it always matches the source. * marks a required prop.
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | number | — | Controlled value. |
| duration | number | 1400 | Animation length in milliseconds (or seconds where noted). |
| decimals | number | 0 | Fraction digits to display. |
| suffix | string | "" | Text appended to the number. |
| prefix | string | "" | Text prepended to the number. |
| className | string | — | Extra classes, merged with the component's own. |
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
/** Number that eases up from zero the first time it scrolls into view. */
export function CountUp({
value,
duration = 1400,
decimals = 0,
suffix = "",
prefix = "",
className,
}: {
value: number;
duration?: number;
decimals?: number;
suffix?: string;
prefix?: string;
className?: string;
}) {
const ref = React.useRef<HTMLSpanElement>(null);
const [display, setDisplay] = React.useState(0);
const [started, setStarted] = React.useState(false);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) {
setStarted(true);
io.disconnect();
}
},
{ threshold: 0.4 },
);
io.observe(el);
return () => io.disconnect();
}, []);
React.useEffect(() => {
if (!started) return;
const start = performance.now();
let raf = 0;
const tick = (now: number) => {
const t = Math.min(1, (now - start) / duration);
setDisplay(value * (1 - Math.pow(1 - t, 4)));
if (t < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [started, value, duration]);
return (
<span
ref={ref}
aria-label={`${prefix}${value}${suffix}`}
className={cn("font-medium tabular-nums tracking-[-0.04em] text-ink", className)}
>
{prefix}
{display.toLocaleString("en-US", {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
})}
{suffix}
</span>
);
}
Exports CountUp. Peer dependencies: clsx, tailwind-merge.