motion-counter

Animates a number from one value to another using spring physics when it enters the viewport. Great for stats, scores, and dashboards.

Preview

Active users
Revenue
Satisfaction

Import

import 'motion-components/motion-counter'   // just this one
import 'motion-components'               // or the whole library

Usage

<motion-counter from="0" to="9847" duration="1.8" suffix="+"></motion-counter>

How it works

The counter watches the viewport via IntersectionObserver. When 20% of the element is visible, it animates the number from from to to using a spring. By default (once), it only fires once and disconnects the observer.

Prefix & suffix

Attach static text before or after the number. This is useful for currency symbols, units, and percentages.

<!-- Currency -->
<motion-counter from="0" to="2.4" decimals="1" prefix="$" suffix="M"></motion-counter>

<!-- Percentage -->
<motion-counter from="0" to="98" suffix="%"></motion-counter>

<!-- Score -->
<motion-counter from="0" to="9847" suffix="+"></motion-counter>

Decimals

Control how many decimal places are displayed. The spring animates through fractional values so the decimal digits flow naturally.

<motion-counter from="0" to="4.87" decimals="2" suffix=" GB"></motion-counter>

Repeat on re-entry

once defaults to true. Set once="false" to count again every time the element re-enters the viewport. From JavaScript, set the once property to false.

<motion-counter from="0" to="100" once="false"></motion-counter>

Loop

Add loop to count to to, hold, snap back to from and count again, like a dashboard that keeps ticking. hold sets how long the final number stays, gap how long it sits back at the start.

Requests per second:

<motion-counter from="120" to="480" loop hold="1.2" gap="0.4"></motion-counter>

With loop the cycle runs only while the counter is on screen, and once is ignored. Add pause-on-hover to freeze the cycle while the pointer is over it.

Methods

Method Description
replay() Restart the animation from from immediately, regardless of viewport state.
const counter = document.querySelector('motion-counter')
counter.replay()

Properties

Attribute Property Type Default Description
from from number 0 Starting value
to to number 100 End value
duration duration number 1.5 Spring duration hint in seconds
decimals decimals number 0 Number of decimal places to display
prefix prefix string '' Static text prepended to the number
suffix suffix string '' Static text appended to the number
once once boolean true Animate only on first intersection. Set to false to replay on every re-entry. Ignored with loop
loop loop boolean false Count to to, hold, snap back to from and count again on repeat. The cycle runs while the counter is on screen
hold hold number 1.6 With loop, seconds the counter stays at to before snapping back
gap gap number 0.5 With loop, seconds the counter stays at from before counting again
pause-on-hover pauseOnHover boolean false Pause the loop while the pointer is over the counter, resuming on leave

JavaScript API

Trigger the counter programmatically with play() and cancel().

–

Accessibility

When prefers-reduced-motion is set, the counter jumps instantly to its final value with no animation, and loop never starts.