• Home
  • Blog
  • Contact
  • Book a call

© Copyright 2026 | Vladyslav Dihtiarenko | All Rights Reserved

How to create Animated Number Counter with React and Framer Motion

May 2, 2024

•

#react

#animations

•

2 mins read

In this blog post, we'll guide you through building a reusable counter animation component using Framer Motion in React. This animation can be a powerful tool for showcasing metrics on your website, like total revenue, positive reviews, or completed projects, in a way that grabs attention.

Building a Reusable Component

Following DRY (Don't Repeat Yourself) principles, we'll create a reusable component named AnimatedCounter.tsx to handle the animation logic. This component will accept props to customize the animation and display the counter.

Here's the basic structure of the component:

type AnimatedCounterProps = {
  from: number;
  to: number;
  animationOptions?: KeyframeOptions;
};

function AnimatedCounter({
  from = 0,
  to,
  animationOptions,
}: AnimatedCounterProps) {
  return <span />;
}

export default AnimatedCounter;

As you can see, the component receives a starting value (from), an ending value (to), and optional animation options (animationOptions). It then returns a simple span element. We'll discuss the reason behind this seemingly empty element later.

Animating the Counter

To create the animation, we'll use Framer Motion's imperative approach. Here's what's involved:

  1. Attaching a ref: A ref allows us to access the DOM element of the span within our code.
  2. Using useLayoutEffect hook: This hook helps manage the animation logic (use useIsomorphicLayoutEffect for server-side rendering).
  3. Animating with animate: The animate function from Framer Motion brings the numbers to life with the animation.
  4. Updating the displayed number: Inside the animate function, we'll use the onUpdate event to update the displayed value as the animation progresses, creating the count-up effect.
  5. Optional viewport-based triggering (using useInView): This functionality ensures the animation only plays when the component is visible on the screen, improving performance.

The complete code for the AnimatedCounter component looks like this:

"use client";

import {
  KeyframeOptions,
  animate,
  useInView,
  useIsomorphicLayoutEffect,
} from "framer-motion";
import { useRef } from "react";

type AnimatedCounterProps = {
  from: number;
  to: number;
  animationOptions?: KeyframeOptions;
};

function AnimatedCounter({
  from = 0,
  to,
  animationOptions,
}: AnimatedCounterProps) {
  // Create a ref for our span element
  const ref = useRef<HTMLSpanElement>(null);

  // Fire animation when in viewport (but only once)
  const inView = useInView(ref, { once: true });


  useIsomorphicLayoutEffect(() => {
    const element = ref.current;

    if (!element) return;
    if (!inView) return; // Don't fire the animation if outside the viewport

    const controls = animate(from, to, {
      // Default animation options
      duration: 1.2,
      ease: "easeOut",
      // Overrides, if passed through props
      ...animationOptions,
      // Continiously set a new number while animating
      onUpdate(value) {
        element.textContent = value.toFixed(0);
      },
    });

    return () => controls.stop();
  }, [from, to, inView]); // If props are changed, restart the animation

  return <span ref={ref} />;
}

export default AnimatedCounter;
☝🏻Pro Tip: Using a monospace font for number animations helps prevent unexpected slight jumps in the UI caused by varying character widths in different fonts.

Respecting User Preferences for Reduced Motion

While our animation works great, it might not be ideal for users who have enabled reduced motion settings on their devices. To ensure a good user experience, we can implement this check in two ways:

  1. Using media query: We can check for the prefers-reduced-motion media query within the useLayoutEffect hook. If reduced motion is enabled, we simply display the final counter value without animation.
  2. Custom hook for reduced motion: Alternatively, we can create a custom hook that detects reduced motion settings and conditionally render the component.

Both approaches achieve the same result. Choose the one that best suits your project structure.

This concludes the tutorial! With this technique, you can now create beautiful and engaging counter animations to enhance your website's storytelling. Go forth and impress your visitors!

Useful resources:

  1. useEffect vs useLayoutEffect
  2. animate function
  3. onUpdate event
  4. prefers-reduced-motion