Progress Indicator

Progress indicators let people know that your app isn't stalled while it loads content or performs lengthy operations.
Apple
Apple
import { Stack } from 'expo-router';
import * as React from 'react';
import { View, Text, ScrollView } from 'react-native';

import { ProgressIndicator } from '@/components/nativewindui/ProgressIndicator';
import { useColorScheme } from '@/lib/useColorScheme';

export default function ProgressIndicatorScreen() {
  const [progress, setProgress] = React.useState(13);
  let id: ReturnType<typeof setInterval> | null = null;
  React.useEffect(() => {
    if (!id) {
      id = setInterval(() => {
        setProgress((prev) => (prev >= 99 ? 0 : prev + 5));
      }, 1000);
    }
    return () => {
      if (id) clearInterval(id);
    };
  }, []);
  const { colors } = useColorScheme();
  return (
    <>
      <Stack.Screen
        options={{
          title: 'NativewindUI',
          headerSearchBarOptions: {
            hideWhenScrolling: false,
          },
          headerLargeTitle: true,
          headerTransparent: true,
        }}
      />
      <ScrollView contentInsetAdjustmentBehavior="automatic" className="p-4">
        <View className="border-border bg-card gap-4 rounded-xl border p-4 pb-6 shadow-sm shadow-black/10 dark:shadow-none">
          <Text className="text-foreground text-center text-sm font-medium tracking-wider opacity-60">
            Progress Indicator
          </Text>
          <View className="p-4">
            <ProgressIndicator value={progress} />
          </View>
        </View>
      </ScrollView>
    </>
  );
}

Installation

1

Run the following command:

npx nwui-cli@latest add progress-indicator
🚀
Ship.
1

Copy/paste the following code to the specified file path:

@/components/nativewindui/ProgressIndicator.tsx
import { View, type ViewProps } from 'react-native';
import Animated, {
  Extrapolation,
  interpolate,
  useAnimatedStyle,
  useDerivedValue,
  withSpring,
} from 'react-native-reanimated';

import { cn } from '@/lib/cn';

const DEFAULT_MAX = 100;

function ProgressIndicator({
  value: valueProp,
  max: maxProp,
  getValueLabel = defaultGetValueLabel,
  className,
  children,
  ...props
}: ViewProps & {
  value?: number;
  max?: number;
  getValueLabel?: (value: number, max: number) => string;
}) {
  const max = maxProp ?? DEFAULT_MAX;
  const value = isValidValueNumber(valueProp, max) ? valueProp : 0;
  const progress = useDerivedValue(() => value ?? 0);

  const indicator = useAnimatedStyle(() => {
    return {
      width: withSpring(
        `${interpolate(progress.value, [0, max], [1, 100], Extrapolation.CLAMP)}%`,
        { overshootClamping: true }
      ),
    };
  });

  return (
    <View
      role="progressbar"
      aria-valuemax={max}
      aria-valuemin={0}
      aria-valuenow={value}
      aria-valuetext={getValueLabel(value, max)}
      accessibilityValue={{
        min: 0,
        max,
        now: value,
        text: getValueLabel(value, max),
      }}
      className={cn('relative h-1 w-full overflow-hidden rounded-full', className)}
      {...props}>
      <View className="bg-muted absolute bottom-0 left-0 right-0 top-0 opacity-20" />
      <Animated.View role="presentation" style={indicator} className={cn('bg-primary h-full')} />
    </View>
  );
}

export { ProgressIndicator };

function defaultGetValueLabel(value: number, max: number) {
  return `${Math.round((value / max) * 100)}%`;
}

function isValidValueNumber(value: any, max: number): value is number {
  return typeof value === 'number' && !isNaN(value) && value <= max && value >= 0;
}
🛸
Ship.

Usage

import { ProgressIndicator } from '@/components/nativewindui/ProgressIndicator';
<ProgressIndicator value={50} />

Props

ProgressIndicator

Inherits all the props from React Native's View component.

PropTypeDescription
valuenumberThe current value to be represented.
maxnumberThe maximum value for the range.
getValueLabel(value: number, max: number) => stringA function to format the value label based on the current value and maximum.
© Ronin Technologies LLC 2025