Rolling number
Free · MITReact Native · ExpoA number whose digits roll in the direction of change, with digits that open and close in place and currency signs that glide instead of jumping.
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.Installation
$ npx shadcn@latest add https://goodui.dev/native/r/rolling-number.jsonThen run npx expo install --fix so react-native-reanimated match your Expo SDK. Files land in components/goodui-native/rolling-number.
Usage
import { View } from "react-native";
import { Text } from "@/components/goodui-native/foundation";
import { RollingNumber } from "@/components/goodui-native/rolling-number/rolling-number";
<View style={{ flexDirection: "row", alignItems: "flex-end", gap: 8 }}>
<RollingNumber
value={yearly ? 79.99 : 9.99}
size="largeTitle"
weight="medium"
locale="en-US"
format={{ style: "currency", currency: "USD" }}
/>
<Text tone="secondary" style={{ marginBottom: 6 }}>
{yearly ? "/ year" : "/ month"}
</Text>
</View>When to use
Use it for
- Prices that change with a plan or billing period.
- Totals, balances, counters and scores that update in place.
Reach for something else
- Static numbers that never change: use Text with tabular digits.
- Values that update many times a second: use a plain Text.
API reference
| Prop | Type | Default |
|---|---|---|
| value* The number to show. Changes roll the digits. | number | — |
| format Intl options, for example `{ style: "currency", currency: "EUR" }`. | Intl.NumberFormatOptions | — |
| locale BCP 47 locale for separators and currency placement, for example `de-DE`. Defaults to the device locale. | string | — |
| size Type size from the theme scale. | "caption" | "footnote" | "subhead" | "body" | "title3" | "title2" | "title1" | "largeTitle" | "body" |
| weight Regular or medium. | "regular" | "medium" | "regular" |
| tone Text color from the theme. | "success" | "warning" | "default" | "danger" | "accent" | "secondary" | "tertiary" | "default" |
| trend Which way digits roll. `auto` rolls up when the value grows and down when it shrinks, even across a 9 to 0 boundary. | "auto" | "up" | "down" | "auto" |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- Each digit is a strip that rolls on the morph spring; increases roll up and decreases roll down, even across 9 and 0.
- A digit that joins opens from zero width and one that leaves closes, so text beside the number reflows smoothly.
- A new currency or locale morphs: the old sign closes while the new one opens.
- Reduced motion: values change in place without rolling.
- One Text node per digit. Rolling runs on the UI thread; joining and leaving digits animate their real width.
Accessibility
- Read as one piece of text with the fully formatted value as its label; the individual digits are hidden.
- On the web the number is visually hidden text and the digit strips are aria-hidden.
Notes for AI
- Pass a number, not a string. Format with the format and locale props (Intl.NumberFormat options).
- It sizes to its content; place labels beside it in a row and they move with it.
- It is a View, so alignItems: 'baseline' does not line it up with neighboring text. Use a row with alignItems: 'flex-end' and give a smaller label a marginBottom of about 6 pt at largeTitle (see the usage example).
- Use the same size and weight tokens as Text.