Docs
Numeric Counter

Numeric Counter

An expanding animated button component.

1,000

Installation

Copy and paste the following code into your project.

npm i clsx tailwind-merge
components/snippet/numeric-counter.tsx
"use client";
 
import { motion, useSpring, useTransform, AnimatePresence } from "framer-motion";
import React, { useEffect, useState } from "react";
 
interface CounterProps {
  value: number;
  className?: string;
}
 
export default function NumericCounter({ value, className }: CounterProps): React.JSX.Element {
  const [previousValue, setPreviousValue] = useState(value);
  const [change, setChange] = useState(0); 
  const [isIncrease, setIsIncrease] = useState(true); 
 
  const spring = useSpring(value, { mass: 0.8, stiffness: 75, damping: 15 });
  const display = useTransform(spring, (current) =>
    Math.round(current as number).toLocaleString(),
  );
 
  useEffect(() => {
    const difference = value - previousValue;
    if (difference !== 0) {
      setChange(Math.abs(difference)); 
      setIsIncrease(difference > 0); 
      setTimeout(() => setChange(0), 1000); 
    }
    setPreviousValue(value);
    spring.set(value);
  }, [spring, value, previousValue]);
 
  return (
    <div className={className}>
      <motion.span>{display}</motion.span>
      <AnimatePresence>
        {change > 0 && (
          <motion.span
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -20 }}
            transition={{ duration: 0.5 }}
            className={`text-sm absolute -top-10 translate-x-1/2 ${isIncrease ? "text-gray-500" : "text-gray-500"
              }`}
          >
            {isIncrease ? "+" : "-"}{change.toLocaleString()}
          </motion.span>
        )}
      </AnimatePresence>
    </div>
  );
}