Animated Count

A number that springs from its previous value to a new target on every change. Drop it in next to anything that ticks — score, balance, count, percentage — and let the value settle instead of flashing.

0
Customize
Target
1234
Motion
smooth
Format

Installation

npx shadcn@latest add https://craftbits.dev/r/animated-count.json

Usage

import { AnimatedCount } from "@craft-bits/core";
 
<AnimatedCount value={1234} />

Pass format for currency, percentages, or any custom display:

import { SPRINGS } from "@craft-bits/core/motion";
 
<AnimatedCount
  value={amount}
  transition={SPRINGS.bouncy}
  format={(v) => `$${v.toFixed(2)}`}
/>

Understanding the component

  1. Motion-value driven. A useMotionValue holds the current animated number. On every value change, animate(mv, value, transition) springs the motion-value toward the new target without remounting the DOM node.
  2. Format runs every frame. useTransform(mv, format) maps the raw number to a display string on each frame. The component mirrors that string into React state so the DOM re-renders as the spring settles.
  3. Spring transition. Defaults to SPRINGS.smooth. Swap to SPRINGS.bouncy for celebratory counts or SPRINGS.snap for crisp tick-ups.
  4. Tabular numerics. The rendered span uses font-variant-numeric: tabular-nums so digits don't shift width while ticking — the counter stays locked to a fixed grid.
  5. Reduced motion. When prefers-reduced-motion: reduce is set, the value snaps instantly to its new target — the spring is skipped entirely.

Props

PropTypeDefaultDescription
valuenumberrequiredThe target value. Each change springs from the previous target.
transitionTransitionSPRINGS.smoothFramer transition (use a named spring from @craft-bits/core/motion).
format(v: number) => stringMath.round(v).toString()Transform the raw motion-value into a display string.
classNamestringMerged onto the rendered motion.span.

Accessibility

  • The component renders a plain <span> whose text content updates frame-by-frame. Screen readers will not announce intermediate values.
  • This component does not set aria-live. The surrounding context owns announcement policy — wrap the count in an element with aria-live="polite" only when you actually want announcements.
  • Animation is fully disabled when prefers-reduced-motion: reduce is set — the value snaps to its new target with no spring.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/viz/AnimatedCount.tsx).