expo-content-transition: Native Numeric Text Transitions for Expo

Description:

expo-content-transition is a React Native and Expo component that animates changing text through a native NumericText view. It rolls, scales, blurs, and staggers individual glyphs.

Numeric values align around the decimal separator, so a small change can animate only the characters that changed.

See it in action

Features

  • Rolls, scales, blurs, and staggers individual glyphs.
  • Aligns numeric characters around a configurable decimal separator.
  • Keeps shared prefixes fixed when the displayed content is non-numeric.
  • Supports automatic, upward, and downward roll direction.
  • Adds bounce, entry scale, travel distance, per-glyph blur, clipping, and instant updates.
  • Uses monospaced digit columns when neighboring values must keep their positions.
  • Blends frequent value changes into the current transition.
  • Accepts font, color, alignment, spacing, and React Native text styles.

How To Use It

Install and Build the Native Module

bun install expo-content-transition
bunx expo prebuild
bunx expo run:ios
# Or use bunx expo run:android

Run expo run:ios or expo run:android again after installing into a project that already has native folders.

Basic Numeric Counter

Pass a number or string through value. The first value renders immediately. Later changes use the configured transition.

import { useState } from "react";
import { Pressable, Text, View } from "react-native";
import { NumericText } from "expo-content-transition";
export default function ScoreCard() {
  const [score, setScore] = useState(128);
  return (
    <View>
      <NumericText
        value={score}
        color="#F8FAFC"
        fontSize={64}
        fontWeight="700"
        monospacedDigits
      />
      <Pressable onPress={() => setScore((current) => current + 1)}>
        <Text>Increment</Text>
      </Pressable>
    </View>
  );
}

Animate a Stopwatch

A fast-changing string keeps its time separators in place. The component blends updates that arrive before an earlier transition finishes.

import { useEffect, useState } from "react";
import { Pressable, Text, View } from "react-native";
import { NumericText } from "expo-content-transition";
function pad(value: number) {
  return String(value).padStart(2, "0");
}
export default function Stopwatch() {
  const [running, setRunning] = useState(false);
  const [ticks, setTicks] = useState(0);
  useEffect(() => {
    if (!running) return;
    const timer = setInterval(() => {
      setTicks((current) => current + 1);
    }, 100);
    return () => clearInterval(timer);
  }, [running]);
  const display = `${pad(Math.floor(ticks / 600))}:${pad(Math.floor(ticks / 10) % 60)}.${ticks % 10}`;
  return (
    <View>
      <NumericText
        value={display}
        color="#E2E8F0"
        fontSize={48}
        fontWeight="500"
        monospacedDigits
      />
      <Pressable onPress={() => setRunning((current) => !current)}>
        <Text>{running ? "Stop" : "Start"}</Text>
      </Pressable>
    </View>
  );
}

Numeric Alignment and Text Styling

Numeric strings align around decimalSeparator. The default separator is a period. Set another character when the displayed value uses a different convention.

monospacedDigits assigns uniform widths to digit columns. Use it for counters and timers where a changing digit must not shift neighboring columns.

The base text props:

PropTypeDefaultPurpose
colorstringPlatform colorSets the text color.
fontSizenumberNot specifiedSets the size in scale-independent pixels.
fontWeightFontWeightnormalAccepts normal, bold, or 100 through 900.
fontStyleFontStylenormalAccepts normal or italic.
fontFamilystringNot specifiedResolves Expo fonts, bundled fonts, or platform fonts like React Native Text.
letterSpacingnumberNot specifiedAdds character spacing in points.
monospacedDigitsbooleanfalseKeeps digit columns at uniform widths.
alignmentAlignmentstartAccepts start, center, or end.
decimalSeparatorstring.Sets the character used to align whole and fractional parts.
styleTextStyleNot specifiedApplies text props to glyphs and other style values to the view.

Transition Props

Pass these props when the default motion does not match the interface.

PropTypeDefaultPurpose
directionDirectionautoRolls up as the value grows and down as it shrinks. Use up or down to force a direction.
durationnumber420Sets the nominal duration in milliseconds and scales the internal spring.
bouncenumber0.46Sets roll overshoot from 0 to 0.95.
enterScalenumber0.4Sets the entry size of a new glyph. Use 1 for a pure roll.
travelnumber0.333Sets vertical travel as a fraction of line height. Use 0 to remove vertical movement.
blurbooleantrueBlurs each glyph during its transition.
blurIntensitynumber1Scales blur from 0 to 8. A value of 0 matches blur={false}.
maxBlurRadiusnumberUnboundedSets a ceiling for the blur radius in dp.
clipbooleantrueClips each glyph to its line box.
animatedbooleantrueApplies updates immediately when set to false.

FAQs

Q: Can expo-content-transition run in Expo Go?

A: No. It contains native iOS and Android code. Use a development build or a bare workflow after prebuild.

Q: What happens on the Web?

A: The Web implementation falls back to a plain React Native Text component. It does not animate transitions.

Q: Why do digits shift when the value changes?

A: Set monospacedDigits to true for fixed-width digit columns. Set alignment to keep the whole value anchored at start, center, or end.

Q: Why is blur missing on an Android device?

A: The blur effect requires Android 12, API 31 or newer. Lower API levels ignore blur.

Q: How do I disable motion for a final state or snapshot?

A: Pass animated={false}. The first value is immediate even when animation is enabled.

Add Comment