Animated counter
Give changing totals a clear sense of movement.
datamotion
Preview
Animated counter preview
Live · keyboard ready
Downloads121,408Premium seats32,768Median render1.8 ms
When to use
- Standalone numbers that change, such as totals, prices, or live counts.
- Hero stats that roll up from zero when they scroll into view, via animateOnView.
When not to use
- Use metric-card or stat-card when the number needs a card around it.
- Use text-morph for words rather than numbers.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "animated-counter",
"type": "registry:ui",
"title": "Animated counter",
"description": "Give changing totals a clear sense of movement.",
"categories": [
"data"
],
"dependencies": [
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/animated-counter/animated-counter.tsx",
"type": "registry:component",
"target": "@components/manicat/animated-counter/animated-counter.tsx"
},
{
"path": "registry/components/animated-counter/animated-counter.module.css",
"type": "registry:component",
"target": "@components/manicat/animated-counter/animated-counter.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/animated-counter",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/animated-counter",
"markdown": "https://uiarc.dev/components/animated-counter/markdown",
"tags": [
"data",
"motion"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { AnimatedCounter } from "@/components/manicat/animated-counter/animated-counter";
export function AnimatedCounterExample() {
return (
<div className="flex w-full max-w-md flex-col gap-6">
<AnimatedCounter value={121_408} label="Downloads" />
<AnimatedCounter value={32_768} label="Premium seats" />
<AnimatedCounter value={1.8} decimals={1} suffix=" ms" label="Median render" />
</div>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Live stats Numbers that count up to their value, with units and decimals.
Live stats
Numbers that count up to their value, with units and decimals. preview
Live · keyboard ready
Downloads this month121,408Uptime99.98%Median response1.8 ms
live-stats-numbers-that-count-up-to-their-value-with-units-and-decimals-.tsxtsx
"use client";
import { AnimatedCounter } from "@/components/manicat/animated-counter/animated-counter";
export function AnimatedCounterStatsExample() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<AnimatedCounter value={121_408} label="Downloads this month" />
<AnimatedCounter value={99.98} decimals={2} suffix="%" label="Uptime" />
<AnimatedCounter value={1.8} decimals={1} suffix=" ms" label="Median response" />
</div>
);
}
Accessibility
- A visually hidden copy holds the full formatted text; the rolling digits are aria-hidden.
- It does not announce changes; wrap it in a live region if updates must be spoken.
Motion
- Each digit column turns in the direction the whole number moved, wrapping 9 to 0, with a slight stagger on first reveal.
- Columns and separators slide in or out when the digit count changes.
- Reduced motion jumps digits into place.
Responsive behavior
- It sizes to its digits, and columns slide in or out when the digit count changes, so neighbours shift slightly.
Performance
- Each digit is its own motion column; fine for a few counters, not for every cell of a table.
- The first reveal waits for 60% visibility through useInView.
Notes for AI
- Use for standalone numbers that change: totals, prices, counts. Use metric-card or stat-card when the number needs a card around it.
- Pass a raw number and let decimals, prefix, and suffix format it; do not pass preformatted strings.