Skip to content

Counter

import { Counter } from '@delightstack/components';
0
View code
<Counter value={1234} />

The prefix is rendered smaller and top-aligned, like a superscript.

$ 0
View code
<Counter value={1234567} prefix="$" />
0.0 %
View code
<Counter value={87.5} suffix="%" decimals={1} />

Override the default Intl.NumberFormat with a custom function for compact display.

<Counter
value={1500000}
format={(n) => {
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
return n.toString();
}}
/>

Number separators adapt to the specified locale.

0,00
View code
<Counter value={1234.56} locale="de-DE" decimals={2} />
0
View code
<Counter value={9999} duration={2000} delay={500} />

value is optional — it may arrive late (e.g. while stats load). With skeleton set, a number-shaped shimmer pill shows until value is available, and the count-up starts the moment it arrives.

View code
<Counter skeleton value={loading ? undefined : 12845} />
PropTypeDefaultDescription
valuenumber-Target number to display; may arrive late (pair with skeleton)
durationnumber1500Animation duration in milliseconds
delaynumber0Delay before animation starts (ms)
decimalsnumber0Number of decimal places
prefixstring-Text before the number (rendered small + top)
suffixstring-Text after the number (rendered small + top)
format(n: number) => string-Custom formatter (overrides Intl.NumberFormat)
separatorbooleantrueUse locale thousands separator
localestring-BCP 47 locale for number formatting
easing(t: number) => numberquintOutAnimation easing function
skeletonbooleanfalseShow a number-shaped shimmer pill until value is available; the count-up starts when it arrives. Pill width is set by --counter-skeleton-width (default 4ch)
idstringautoElement ID
classstring''Additional CSS classes
MethodDescription
restart()Reset and replay the count animation from 0 to value. Useful for demos and on-demand replays.
EventDetailDescription
oncomplete-Fired when the animation finishes
  • Element has aria-live="polite" so screen readers announce value changes
  • role="img" with a computed aria-label including prefix, value, and suffix
  • The true value is always exposed via aria-label, so assistive tech reads it regardless of the animated visual value
  • prefers-reduced-motion disables all animation — the final value is shown immediately
  • font-variant-numeric: tabular-nums prevents layout shift during animation
  • Animation is triggered by IntersectionObserver — starts only when the element scrolls into view
  • On the server (SSR) the counter renders at 0, matching the initial client render so hydration is seamless; it then counts up once visible (with JS disabled it stays at 0, but the real value remains in aria-label)