beta

Number Ticker — <mv-number-ticker>

Animated counter formatted with Intl (en-US by default: currency, percent, compact, decimals) in two modes: smooth interpolation or rolling odometer; re-animates on every value change, with no layout shift.

CategoryAnimated text
TypeWeb Component (<mv-number-ticker>)
Statusbeta
Keywordscounter, count-up, odometer, stats, kpi, animated-number, slot

When to use

Avoid when

Install

node scripts/add.mjs number-ticker --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["number-ticker"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/number-ticker/number-ticker.js, components/number-ticker/number-ticker.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="nt-demo" style="display:grid;gap:1.5rem;width:min(100%,48rem);margin-inline:auto">
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(10rem,1fr));gap:.75rem">
    <div style="padding:1rem 1.1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface)">
      <div style="font:700 2rem/1.1 var(--mv-font-display);letter-spacing:-.03em"><mv-number-ticker value="12840" prefix="+"></mv-number-ticker></div>
      <div style="margin-top:.25rem;font-size:.85rem;color:var(--mv-fg-muted)">customer teams</div>
    </div>
    <div style="padding:1rem 1.1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface)">
      <div style="font:700 2rem/1.1 var(--mv-font-display);letter-spacing:-.03em"><mv-number-ticker value="4.9" suffix="/5" delay="150"></mv-number-ticker></div>
      <div style="margin-top:.25rem;font-size:.85rem;color:var(--mv-fg-muted)">average rating</div>
    </div>
    <div style="padding:1rem 1.1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface)">
      <div style="font:700 2rem/1.1 var(--mv-font-display);letter-spacing:-.03em"><mv-number-ticker value="99.98" format="percent" delay="300" easing="var(--mv-ease-in-out)"></mv-number-ticker></div>
      <div style="margin-top:.25rem;font-size:.85rem;color:var(--mv-fg-muted)">uptime</div>
    </div>
    <div style="padding:1rem 1.1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface)">
      <div style="font:700 2rem/1.1 var(--mv-font-display);letter-spacing:-.03em"><mv-number-ticker value="2400000" format="currency" compact delay="450"></mv-number-ticker></div>
      <div style="margin-top:.25rem;font-size:.85rem;color:var(--mv-fg-muted)">raised in seed</div>
    </div>
  </div>

  <div style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:1rem;padding:1.25rem 1.4rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:linear-gradient(to bottom,var(--mv-surface-raised),var(--mv-bg-subtle));box-shadow:var(--mv-shadow-sm)">
    <div>
      <div style="font-size:.8rem;letter-spacing:.08em;text-transform:uppercase;color:var(--mv-fg-muted)">Today’s revenue</div>
      <div style="margin-top:.2rem;font:800 clamp(2.2rem,6vw,3.4rem)/1.1 var(--mv-font-display);letter-spacing:-.04em">
        <mv-number-ticker id="nt-demo-live" mode="roll" value="48265.90" format="currency"></mv-number-ticker>
      </div>
    </div>
    <button class="mv-button" id="nt-demo-sale">+ Simulate a sale</button>
  </div>
</div>
<script type="module">
  const live = document.getElementById("nt-demo-live");
  document.getElementById("nt-demo-sale").addEventListener("click", () => {
    const next = Number(live.value) + Math.round(40 + Math.random() * 900) + Math.round(Math.random() * 99) / 100;
    live.value = next.toFixed(2);
  });
</script>

API

Attributes

NameTypeDefaultDescription
valuenumber0Target value. Changing it restarts the animation from the displayed value.
fromnumber0Starting value of the first animation.
modecount | rollcountcount = interpolation; roll = odometer (each digit rolls vertically).
formatdecimal | currency | percent | compactdecimalIntl style. percent expects percentage points (42 → 42%).
compactbooleanCompact notation, combinable (e.g. currency + compact → $2.4M).
currencyISO 4217 codeUSDCurrency for format=currency.
localeBCP 47en-USFormatting locale (separators, symbols).
decimalsnumber(inferred from value/from)Number of decimals shown.
durationnumber (ms)2000 (count) / 1400 (roll)Animation duration.
delaynumber (ms)0Delay before the first animation.
staggernumber (ms)60Offset between reels (roll mode).
easingCSS easingvar(--mv-ease-out)Any CSS curve, including var(--mv-ease-spring) (linear()).
triggerview | load | manualviewWhat starts the first animation. manual waits for replay().
prefix / suffixstringFixed text around the number (e.g. +, /5).

Methods

NameDescription
replay()Replays from from to value.

Events

NameDescription
mv-endAnimation finished (detail.value).

CSS variables

NameDefaultDescription
--mv-number-ticker-alignendAlignment of the number within its reserved width (count mode).
--mv-number-ticker-fade0.14emHeight of the top/bottom fade on the reels.

Accessibility

The final formatted value (prefix and suffix included) lives in a hidden copy; the animated digits are aria-hidden, so they are never read out in bursts. Tabular digits and reserved width: no jitter. With reduced motion, the final value shows immediately.