RollingNumber

RollingNumber animates changing digits in a numeric readout.

Basics

A counter whose digits roll to their new positions. Only the columns that changed move.

Loading demo…

import { useState } from 'react'; import { Button, Flex, RollingNumber } from '@plocks/ui'; export function Demo() { const [value, setValue] = useState(1234); return ( <Flex direction="column" align="center" gap="md"> <RollingNumber value={value} size={48} fw="bold" /> <Button variant="outline" onPress={() => setValue((current) => current + 1)}>+1</Button> </Flex> ); }

Currency

prefix, suffix and the decimal options cover currency formatting without an external formatter. Copying the value on web yields the formatted string, not the digit strips.

Loading demo…

import { useState } from 'react'; import { Button, Flex, RollingNumber } from '@plocks/ui'; export function Demo() { const [total, setTotal] = useState(1299.99); return ( <Flex direction="column" align="center" gap="md"> <RollingNumber value={total} prefix="$ " suffix=" USD" decimalScale={2} fixedDecimalScale thousandSeparator size={36} fw="semibold" /> <Button variant="outline" onPress={() => setTotal((current) => current + 149.5)}> Add item </Button> </Flex> ); }

Timing

transitionDuration, timingFunction and stagger shape the roll. Stagger delays each column right-to-left, so the carries trail the ones place the way an odometer does.

Loading demo…

import { useState } from 'react'; import { Button, Flex, RollingNumber, Text } from '@plocks/ui'; export function Demo() { const [value, setValue] = useState(407219); return ( <Flex direction="column" gap="lg"> <Flex direction="column" gap="xs"> <Text size="xs" c="muted">Snappy — 200ms, no stagger</Text> <RollingNumber value={value} transitionDuration={200} size={32} thousandSeparator /> </Flex> <Flex direction="column" gap="xs"> <Text size="xs" c="muted">Odometer — 900ms, 60ms stagger</Text> <RollingNumber value={value} transitionDuration={900} timingFunction="ease-out" stagger={60} size={32} thousandSeparator /> </Flex> <Button variant="outline" onPress={() => setValue(Math.floor(Math.random() * 999999))}> Shuffle </Button> </Flex> ); }

Trend

By default the digits roll up when the number grows and down when it shrinks, so 19 → 20 carries the ones column forward like an odometer. trend={1} or trend={-1} fix the direction, trend={0} moves each digit straight to its new value, and a function (previous, value) => number decides per change.

Loading demo…

import { useState } from 'react'; import { Button, Flex, RollingNumber, Text } from '@plocks/ui'; export function Demo() { const [value, setValue] = useState(19); return ( <Flex direction="column" gap="lg"> <Flex gap="xl"> <Flex direction="column" align="center" gap="xs"> <Text size="xs" c="muted">Auto</Text> <RollingNumber value={value} size={32} /> </Flex> <Flex direction="column" align="center" gap="xs"> <Text size="xs" c="muted">Always up</Text> <RollingNumber value={value} trend={1} size={32} /> </Flex> <Flex direction="column" align="center" gap="xs"> <Text size="xs" c="muted">Per digit</Text> <RollingNumber value={value} trend={0} size={32} /> </Flex> </Flex> <Flex gap="sm" justify="center"> <Button variant="outline" onPress={() => setValue((current) => current - 1)}>−1</Button> <Button variant="outline" onPress={() => setValue((current) => current + 1)}>+1</Button> </Flex> </Flex> ); }

Live metric

A ticking live metric. Values that change faster than the roll retarget mid-flight rather than snapping.

Loading demo…

import { useEffect, useState } from 'react'; import { Flex, RollingNumber, Text } from '@plocks/ui'; export function Demo() { const [requests, setRequests] = useState(84213); useEffect(() => { const timer = setInterval(() => { setRequests((current) => current + Math.floor(Math.random() * 40)); }, 1200); return () => clearInterval(timer); }, []); return ( <Flex direction="column" gap="xs"> <Text size="xs" c="muted" tt="uppercase">Requests today</Text> <RollingNumber value={requests} thousandSeparator size={40} fw="bold" transitionDuration={500} stagger={40} /> </Flex> ); }
Scan to open this page on your phoneplocks.dev/ui/RollingNumber