shadimage
unlumen-ui
uianimate-countsource: ok · refreshed

Vercel Animate Count

A number display that animates the transition between values with a blur + slide effect, with a hook to throttle rapid updates.

npx shadcn add "https://ui.unlumen.com/r/animate-count.json"
free

Visual style

glass-blur

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
animationmotion

Files1 · source cached 10/8/2026, 5:47:46 AM

registry/components/unlumen/animate-count/index.tsx
"use client";

import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "motion/react";

import { cn } from "@/lib/utils";

export const ANIMATE_COUNT_DURATION_MS = 450;

const EASING = [0.23, 0.88, 0.26, 0.92] as const;

interface AnimateCountProps {
  children: number;
  animate?: boolean;
  className?: string;
}

function AnimateCount({
  children: count,
  animate = true,
  className,
}: AnimateCountProps) {
  const [prev, setPrev] = useState<number | null>(null);
  const [displayCount, setDisplayCount] = useState(count);

  useEffect(() => {
    if (animate) setPrev(displayCount);
    setDisplayCount(count);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [count, animate]);

  return (
    <div
      className={cn(
        "grid place-items-center tabular-nums tracking-tight",
        "[&>*]:col-start-1 [&>*]:row-start-1",
        className,
      )}
    >
      <AnimatePresence initial={false}>
        {animate && prev !== null && prev !== displayCount && (
          <motion.div
            key={`exit-${prev}-${displayCount}`}
            aria-hidden
            initial={{ opacity: 1, filter: "blur(0px)", y: 0 }}
            animate={{ opacity: 0, filter: "blur(2px)", y: -12 }}
            transition={{
              duration: ANIMATE_COUNT_DURATION_MS / 1000,
              ease: EASING,
            }}
            onAnimationComplete={() => setPrev(null)}
          >
            {prev}
          </motion.div>
        )}
      </AnimatePresence>
      <motion.div
        key={`enter-${displayCount}`}
        initial={animate ? { opacity: 0, filter: "blur(2px)", y: 8 } : false}
        animate={{ opacity: 1, filter: "blur(0px)", y: 0 }}
        transition={{
          duration: ANIMATE_COUNT_DURATION_MS / 1000,
          ease: EASING,
        }}
      >
        {displayCount}
      </motion.div>
    </div>
  );
}

export { AnimateCount, type AnimateCountProps };

More from unlumen-ui

Browse all components in this library →