Rolling Number

Odometer where every place rolls at its own speed, new places rising into view, settling exactly on the target value

Installation

$ pnpm dlx shadcn@latest add @remocn/rolling-number

Usage

// src/Root.tsx
import { Composition } from "remotion";
import { RollingNumber } from "@/components/remocn/rolling-number";

const RollingNumberScene = () => (
  <RollingNumber from={0} to={24813} fontSize={120} />
);

export const RemotionRoot = () => (
  <Composition
    id="RollingNumber"
    component={RollingNumberScene}
    durationInFrames={150}
    fps={30}
    width={1280}
    height={720}
  />
);

Props

PropTypeDefaultDescription
from
number0Starting value. Direction is inferred from whether to is greater or less than from.
to
number24813Target value the number settles on, landing pixel-exact on every place.
fontSize
number120Font size in pixels
color
string"#171717"Text color
speed
number1Multiplier for animation speed

Every place rolls at once, each travelling roughly ten times as far as the place to its left, so the ones spin fastest and the highest place barely turns. As the number grows past each power of ten, the new leading place rises gently into view rather than popping in, and the whole number settles pixel-exact on the target. All values must be integers and non-negative.

Count Up

Rolls upward from 0 to 24813:

import { Backdrop } from "@/components/remocn/backdrop";
import { RollingNumber } from "@/components/remocn/rolling-number";

<Backdrop fill={{ type: "color", value: "#ffffff" }}>
  <RollingNumber from={0} to={24813} fontSize={120} />
</Backdrop>

Count Down

Rolls downward from 24813 to 0:

import { Backdrop } from "@/components/remocn/backdrop";
import { RollingNumber } from "@/components/remocn/rolling-number";

<Backdrop fill={{ type: "color", value: "#ffffff" }}>
  <RollingNumber from={24813} to={0} fontSize={120} />
</Backdrop>

With Confetti

Pair it with Confetti to celebrate the moment the number lands — fire the burst on the frame the count settles (durationInFrames × 0.8):

import { Composition } from "remotion";import { Backdrop } from "@/components/remocn/backdrop";import { Confetti } from "@/components/remocn/confetti";import { RollingNumber } from "@/components/remocn/rolling-number";const RollingNumberConfetti = () => (  <Backdrop fill={{ type: "color", value: "#ffffff" }} shadow="0">    <RollingNumber from={0} to={24813} fontSize={120} />    <Confetti startFrame={168} originY={0.5} particleCount={160} lifetime={42} seed={1} />  </Backdrop>);export const RemotionRoot = () => (  <Composition    id="RollingNumberConfetti"    component={RollingNumberConfetti}    durationInFrames={210}    fps={30}    width={1280}    height={720}  />);

Confetti is a separate component — add it with npx shadcn add @remocn/confetti. Both render as full-canvas overlays, so the burst layers cleanly over the number. Keep startFrame equal to durationInFrames × 0.8 (where the count settles): this 210-frame composition settles at frame 168, leaving the 42-frame lifetime room to play out before it loops.