motif svelteArc

Animated Counter ported

Give changing totals a clear sense of movement.

A number that animates digit changes, odometer-style, with place-value-stable columns.

Live specimen

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.

Installation

pnpm add sveltearc

Usage

<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

PropTypeDefaultDescription
valuenumber—The numeric value to display, formatted per decimals/locale.
labelstring—Small caption above the value.
prefixstring' 'Static symbol rendered before the digits, e.g. "$".
suffixstring' 'Static symbol rendered after the digits, e.g. "%".
decimalsnumber0Fraction digits for Intl.NumberFormat.
animateOnViewbooleanfalseRoll every digit up from zero the first time the counter scrolls into view (Phase 2).
localestring'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