Animated Counter ported
Give changing totals a clear sense of movement.
A number that animates digit changes, odometer-style, with place-value-stable columns.
Loading demo…
Guidance
When to use
- Totals that update in place, such as revenue, active users, or event counts.
- When the number deserves attention but the layout must not shift under it.
When not to use
- For static numbers that never change — plain text is lighter.
- For charting a series over time — use a line, bar, or slope chart.
pnpm add sveltearcUsage
<script lang="ts">
import AnimatedCounter from '$lib/components/animated-counter/animated-counter.svelte';
let revenue = $state(128400);
</script>
<AnimatedCounter value={revenue} label="Monthly revenue" prefix="$" />Variants & Examples
With Prefix
A static currency symbol ahead of the digit columns.
<AnimatedCounter value={128400} prefix="$" />Decimals + Suffix
Fraction digits with a trailing unit symbol.
<AnimatedCounter value={97.5} decimals={1} suffix="%" />With Label
A small caption above the value.
<AnimatedCounter value={42000} label="Active users" />API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | The numeric value to display, formatted per decimals/locale. |
label | string | — | Small caption above the value. |
prefix | string | ' ' | Static symbol rendered before the digits, e.g. "$". |
suffix | string | ' ' | Static symbol rendered after the digits, e.g. "%". |
decimals | number | 0 | Fraction digits for Intl.NumberFormat. |
animateOnView | boolean | false | Roll every digit up from zero the first time the counter scrolls into view (Phase 2). |
locale | string | 'en-US' | Formatting locale. Fixed by default so server and client render the same digits. |
Accessibility
- The formatted value is announced as a single string via an sr-only node; the digit columns are aria-hidden.
- Locales are fixed by default so server and client render the same digits.
Motion
Phase 1 still-state: the final formatted value renders statically, with the digit/column DOM (.column/.sizer/.glyph) and tabular-nums kept as Phase 2 hooks. Phase 2 wires the odometer roll (digit-wheel springs, width morph, label rise/blur swap, animateOnView roll) via @humanspeak/svelte-motion; the prefers-reduced-motion path is instant state and already fully functional.
Notes for AI
- The columns are keyed by place value, not by index — never re-key them.
- Keep the sr-only text in sync with the visible digits; it is the accessible name.
- Do not hand-roll grouping separators; Intl.NumberFormat owns them.
Source of truth: registry/components/animated-counter/animated-counter.tsx