Every example below runs live on the page. Each step adds one idea.
Show a value
NumericText takes a value and sizes itself to it. Keep the value in state and change it like any other prop:
Usage
import { NumericText } from 'expo-content-transition';
import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
export function Counter() {
const [count, setCount] = useState(128);
return (
<View style={styles.row}>
<Pressable style={styles.button} onPress={() => setCount((n) => n - 1)}>
<Text style={styles.icon}>−</Text>
</Pressable>
<NumericText value={count} fontSize={64} fontWeight="600" monospacedDigits />
<Pressable style={styles.button} onPress={() => setCount((n) => n + 1)}>
<Text style={styles.icon}>+</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', alignItems: 'center', gap: 24 },
button: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', backgroundColor: '#F2F2F7' },
icon: { fontSize: 22 },
});The first value appears as is; from then on, every change animates. monospacedDigits gives every digit the same width, so the ones that don't change never shift sideways.
Format it first
value is shown exactly as given (numbers go through String()), so format it however the number should read. Currency symbols, group separators and units animate like any other glyph:
const price = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
<NumericText value={price.format(total)} fontSize={40} fontWeight="700" monospacedDigits />Digits line up around the decimal separator, so $9.99 becoming $10.49 grows a digit on the left instead of reshuffling the line. See Formatting values for locales and compact numbers.
Pick a direction
By default glyphs roll up when the number grows and down when it shrinks. A countdown should always tick the same way, whatever the digits say:
Usage
import { NumericText } from 'expo-content-transition';
import { useEffect, useState } from 'react';
const START = 90;
export function Countdown() {
const [seconds, setSeconds] = useState(START);
useEffect(() => {
const timer = setInterval(() => setSeconds((s) => (s > 0 ? s - 1 : START)), 1000);
return () => clearInterval(timer);
}, []);
const minutes = Math.floor(seconds / 60);
const label = `${minutes}:${String(seconds % 60).padStart(2, '0')}`;
return (
<NumericText
value={label}
direction="down"
fontSize={80}
fontWeight="700"
monospacedDigits
/>
);
}Tune the motion
duration, bounce, enterScale and travel shape the transition. Here the same values change three times over, with different amounts of bounce:
bounce={0}bounce={0.46}bounce={0.9}Try every prop together in the playground, or read Transitions for what each one does.
Words work too
Text without digits swaps glyph by glyph, lined up from the start. Force the direction, since there's no number to compare:
Usage
import { NumericText } from 'expo-content-transition';
const LABELS = { uploading: 'Uploading', processing: 'Processing', published: 'Published' };
export function Status({ state }: { state: keyof typeof LABELS }) {
return (
<NumericText
value={LABELS[state]}
direction="up"
fontSize={28}
fontWeight="600"
bounce={0.2}
/>
);
}Style it like text
Typography can be passed as props or through style, the way you'd style a Text. Layout styles go to the view around the glyphs:
<NumericText
value={score}
style={{ fontSize: 34, fontWeight: '800', color: '#111', marginTop: 12 }}
/>An explicit prop wins over the same value in style. See Typography.