Number Flow React Native
Components

SkiaNumberFlow

Skia canvas-based animated number component with worklet scrubbing

SkiaNumberFlow is the Canvas-based animated number component. It renders using @shopify/react-native-skia and supports worklet-driven scrubbing via SharedValue for zero-latency UI thread updates. Ideal for charts, sliders, gesture-driven UIs, and scenarios with many simultaneous animated numbers.

Import

import { SkiaNumberFlow, useSkiaFont } from 'number-flow-react-native/skia';

SkiaNumberFlow must be rendered inside a Skia Canvas component. It renders Skia primitives (Group, Text), not React Native Views.

Basic Usage

Use the useSkiaFont hook for a guaranteed non-null font. It provides a synchronous system-font fallback via matchFont, so the component renders immediately instead of showing blank until the custom font loads.

Props

Value props (mutually exclusive)

Provide either value (JS-driven) or sharedValue (worklet-driven), not both.

PropTypeDefaultDescription
valuenumberundefinedJS-thread numeric value. Triggers animated transitions on change. Mutually exclusive with sharedValue.
formatIntl.NumberFormatOptionsundefinedOptions passed to Intl.NumberFormat. Only available with value.
localesIntl.LocalesArgumentundefinedLocale(s) for Intl.NumberFormat. Only available with value.
sharedValueSharedValue<string>undefinedWorklet-driven pre-formatted string (e.g. "$42.99"). Updates on the UI thread with no JS bridge crossing. Mutually exclusive with value.

Rendering props

PropTypeDefaultDescription
fontSkFont | null(required)Skia font instance from useFont() or useSkiaFont(). Renders empty until font loads.
colorstring"#000000"Text color as a Skia color string.
xnumber0X position within the Canvas.
ynumber0Y position within the Canvas (baseline of the text).
widthnumber0Available width for alignment calculations.
textAlign"left" | "right" | "center""left"Text alignment within the available width.
prefixstring""Static string prepended before the number.
suffixstring""Static string appended after the number.
opacitySharedValue<number>undefinedParent opacity for animation coordination.
digitsRecord<number, { max: number }>undefinedPer-position digit constraints. Position 0 = ones, 1 = tens, etc.
tabularNumsbooleanfalseForce equal-width digits by interpolating between min and max digit glyph widths. Equivalent to fontVariant: ['tabular-nums'] on native components.
scrubDigitWidthPercentilenumber (0–1)0.75Controls digit width during worklet-driven scrubbing. 0 = narrowest, 0.5 = average, 1 = widest (no clipping). Only affects digits; symbols keep natural width.

Animation behavior props

All props from AnimationBehaviorProps are supported: trend, animated, respectMotionPreference, continuous, mask, transformTiming, spinTiming, opacityTiming, onAnimationsStart, onAnimationsFinish. See NumberFlow for details.

Worklet Scrubbing

When using sharedValue, the component updates on the UI thread with zero JS bridge overhead. This is ideal for gesture-driven scenarios:

import { View } from 'react-native';
import { Canvas } from '@shopify/react-native-skia';
import { useSharedValue, useDerivedValue } from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { SkiaNumberFlow, useSkiaFont } from 'number-flow-react-native/skia';

function ScrubSlider() {
  const font = useSkiaFont('https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fMZg.ttf', 32);
  const progress = useSharedValue(0);

  const formattedValue = useDerivedValue(() => {
    return `$${(progress.value * 1000).toFixed(2)}`;
  });

  const pan = Gesture.Pan().onUpdate((e) => {
    progress.value = Math.max(0, Math.min(1, e.translationX / 300));
  });

  return (
    <GestureDetector gesture={pan}>
      <View style={{ padding: 32 }}>
        <Canvas style={{ width: 300, height: 50 }}>
          <SkiaNumberFlow
            sharedValue={formattedValue}
            font={font}
            color="#1a1a1a"
            y={40}
            width={300}
            textAlign="center"
          />
        </Canvas>
      </View>
    </GestureDetector>
  );
}

The scrubDigitWidthPercentile prop controls width allocation during scrubbing. The default 0.75 (75th percentile between narrowest and widest digit) balances tightness with avoiding clipping. Lower values produce tighter spacing but may clip wide digits like "0"; higher values add more spacing.

Accessibility

Value changes are auto-announced for screen reader users via AccessibilityInfo.announceForAccessibility. For VoiceOver/TalkBack focus-based reading, set accessibilityLabel on the parent Canvas:

import { useFormattedValue } from 'number-flow-react-native';

const label = useFormattedValue(value, format);

<Canvas accessible accessibilityLabel={label} style={{ height: 50, width: 200 }}>
  <SkiaNumberFlow value={value} font={font} format={format} />
</Canvas>

On this page