Quick start

A value on screen, formatted, tuned and styled, one idea at a time.

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

Counter.tsx
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

Countdown.tsx
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

Status.tsx
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.

Where to go next