Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Animated Number

A number display that tweens from its previous value to a new one on every change — the dashboard stat-card counter.

Status
stable
Since
0.1.0
Accessibility pattern
decorative text (aria-hidden)

Last updated

Total signups
128

Install

Add Animated Number from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/animated-number

The same command installs the registry items it composes: @vegastack/use-media-query.

Usage

import { AnimatedNumber } from "@/components/ui/animated-number";

<AnimatedNumber value={revenue} />;

Renders the exact value statically on mount — no count-up flash, no hydration mismatch. Every LATER value change tweens the displayed number from whatever is currently shown to the new value, over the duration motion token.

Examples

Currency and compact formats

format takes full Intl.NumberFormatOptions — currency, percent, compact notation, custom grouping. Every intermediate frame of the tween is formatted through the same Intl.NumberFormat(locale, format) instance as the settled value, so the $ symbol, cent grouping, or compact suffix ("12.4K") stay correct throughout the animation, not just at rest.

Revenue this month
$48,250.00
Followers
12.4K

Duration

duration is a motion-token name ('fast' | 'base' | 'slow', default 'base') — resolved from the live --duration-fast/base/slow CSS custom property at animation start, never a hardcoded millisecond value. Easing reads --motion-ease-standard the same way. Overriding either token at runtime (e.g. scoping --duration-base on a container) retunes the tween without a prop.

Interruption

Changing value again before a tween finishes retargets it smoothly from wherever the number currently sits — it never snaps back to an earlier value first.

// Rapid updates (e.g. a live ticker) each interrupt the last, always converging on
// the LATEST value with one continuous motion, not a stutter per update.
<AnimatedNumber
  value={livePrice}
  format={{ style: "currency", currency: "USD" }}
/>

Playground

Pick a different value preset to watch the tween, try the format and duration options, then copy the generated JSX.

1,204
<AnimatedNumber value={1204} />

API Reference

PropTypeDefaultDescription
value*numberThe number to display. On mount it renders statically (no animation). On every later change, the displayed value tweens from whatever is currently shown to this new value. Changing it again mid-tween interrupts the current animation and retargets from the in-flight value — it never snaps back to an earlier value first.
duration"base" | "fast" | "slow"'base'Tween duration, as a motion-token name (never a raw millisecond value) — resolved from the live --duration-{fast,base,slow} CSS custom property at animation start.
formatIntl.NumberFormatOptionsIntl.NumberFormatOptions used to format both the settled value and every animated intermediate frame — e.g. { style: 'currency', currency: 'USD' } or { notation: 'compact' }. Omit for plain locale-grouped digits.
localestring | string[]BCP-47 locale(s) for Intl.NumberFormat. Defaults to the runtime locale.

Data attributes and CSS variables on AnimatedNumber

AttributeValues
data-slot"animated-number" | "animated-number-live" | "animated-number-value"

Accessibility

  • The ticking visual text is aria-hidden — a rapid stream of announcements while a number climbs would be noise, not useful information.
  • A visually-hidden role="status" / aria-live="polite" region announces only the settled value, once per tween completion — never the intermediate frames.
  • prefers-reduced-motion: reduce renders every value change instantly, with no tween at all — the same SSR-safe matchMedia hook used by TruncatedText's no-hover detection and MessageScroller's reduced-motion handling.
  • Digits use tabular-nums so the layout never jitters horizontally as the character count changes mid-animation (e.g. 99100).
  • Server-safe: the initial render (server AND client) always shows the static final value — there is no hydration mismatch to reconcile.
ContractStates tested
Behaviourdefault, empty
Accessibilitylive, status-announcement, semantic-html
Visualdefault, empty

Do / Don't

Do
Re-render with a new value prop to trigger the tween (e.g. after a data refetch or a live-update event) — the component owns the animation, not the caller.
Don't
Roll your own setInterval count-up loop, or format intermediate frames with a different Intl.NumberFormat than the settled value — use format/locale so every frame is correct, not just the final one.

On this page