Number Counter Animation for React

Animated number counter that counts from 0 to target. Tabular nums prevent layout shift, smooth ease-out-expo easing, respects reduced motion.

Component

00+0

Installation

 

Usage

Import

Add the NumberCounter import.

import { NumberCounter } from "@/components/number-counter";

Use

Pass the target value.

<NumberCounter value={9876} />;

Guidelines

  • value: target number to count up to. Animation starts from 0.
  • trigger='inView' (default): animates when scrolled into view. trigger='mount': starts immediately.
  • Use tabular-nums (built-in) and minWidth to prevent layout shift as digits change.
  • suffix and prefix for values like '35+' or '$1,000'.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
valuenumber-Target number to count up to.
classNamestring-Additional CSS classes.
durationnumber1500Animation duration in ms.
trigger"mount" | "inView""inView""mount" = start on mount. "inView" = animate when scrolled into view.
inViewThresholdnumber0.3IntersectionObserver threshold for inView trigger.
suffixstring""Text appended after the number (e.g. '+', 'K').
prefixstring""Text prepended before the number (e.g. '$').

Accessibility

  • Decorative numeric display rendered as a plain span with no interactive controls, buttons, inputs, or focusable elements.
  • No ARIA live region is set, so the rapidly changing digits are not announced to screen readers in a controlled way; consider rendering the final value as accessible text if the count conveys meaningful information.
  • Honours prefers-reduced-motion via window.matchMedia('(prefers-reduced-motion: reduce)'), skipping the animation and jumping straight to the final value when reduced motion is requested.
  • The reduced-motion media query listener is registered and cleaned up on unmount, so live changes to the OS preference are respected.
  • Uses tabular-nums and a minWidth sized in ch units so the displayed number does not cause layout shift or visual jumpiness as digits change.

Performance

  • Animates the numeric text content via setState on each frame rather than a CSS property, so it triggers React re-renders and text reflow inside the span instead of cheap compositor-only transform or opacity changes.
  • Drives the count with requestAnimationFrame and stops scheduling frames once progress reaches 1, avoiding an open-ended timer loop.
  • Defers work until visible by using an IntersectionObserver for the inView trigger, and a hasAnimatedRef guard ensures the animation runs only once.
  • Cleans up both the IntersectionObserver (disconnect) and the matchMedia change listener on unmount, preventing leaked observers and listeners.
  • Layout impact is minimal because the span is fixed-width via minWidth in ch and tabular-nums, so digit changes do not reflow surrounding content; no will-change or explicit GPU compositing hints are used.

Examples

Basic

Counts from 0 to 9876 when scrolled into view.

00+0
import { NumberCounter } from "@/components/number-counter";

<NumberCounter
  value={9876}
  className="font-bold text-2xl"
/>;

With suffix

With suffix for values like 35+.

00+0
import { NumberCounter } from "@/components/number-counter";

<NumberCounter
  value={35}
  suffix="+"
  className="font-bold text-2xl"
/>;

On mount

Starts on mount, 2 second duration.

00+0
import { NumberCounter } from "@/components/number-counter";

<NumberCounter
  value={24}
  trigger="mount"
  duration={2000}
/>;

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: