Skip to content
Corsair UI
Browse React Native

React NativeComponentsNo. 15 of 22

Skeleton

A placeholder shape that pulses on the native thread while content loads, and holds still with Reduce Motion.

For the web: Skeleton, with the same name and variants.

Preview

Loading the preview

The gallery's web build, through react-native-web. On a phone the same code draws native views.

Installation

npx shadcn@latest add @corsair-native/skeleton

The app's components.json needs the @corsair-native registry first; see Installation. The files land in your project as source: edit them like your own code.

Also adds
themeuse-reduced-motion

API

Read from the source's own comments and types. Props marked * are required.

SkeletonComponent

A placeholder shape while content loads: give it the size of what is coming. It pulses between full and half opacity every two seconds on the native thread, and holds still with Reduce Motion. It is hidden from screen readers; mark the loading region with aria-busy instead.

Props of Skeleton
PropTypeDefault
refRef<View>—

Also takes every ViewProps prop (React Native docs).

<Skeleton style={{ width: 160, height: 16 }} />

Compatibility

Expo SDK
54 and 57, both checked in CI
React Native
0.81 and 0.86, New Architecture
Web
Expo's web target, through react-native-web
Motion
Follows Reduce Motion

Source

1 file, installed as source you own
skeleton.tsxView on GitHub
import { useEffect, useState, type Ref } from "react";
import { Animated, Easing, type View, type ViewProps } from "react-native";

import { useReducedMotion } from "@/registry/native/hooks/use-reduced-motion";
import { useTheme } from "@/registry/native/lib/theme";

interface SkeletonProps extends ViewProps {
  ref?: Ref<View>;
}

const PULSE = { duration: 1000, easing: Easing.bezier(0.4, 0, 0.6, 1), useNativeDriver: true };

/**
 * A placeholder shape while content loads: give it the size of what is coming.
 * It pulses between full and half opacity every two seconds on the native
 * thread, and holds still with Reduce Motion. It is hidden from screen
 * readers; mark the loading region with `aria-busy` instead.
 *
 * @example
 * <Skeleton style={{ width: 160, height: 16 }} />
 */
function Skeleton({ style, ...props }: SkeletonProps) {
  const { colors, radius } = useTheme();
  const reduced = useReducedMotion();
  // Not useAnimatedValue, which react-native-web does not have.
  const [opacity] = useState(() => new Animated.Value(1));

  useEffect(() => {
    if (reduced) {
      opacity.setValue(1);
      return;
    }
    const pulse = Animated.loop(
      Animated.sequence([
        Animated.timing(opacity, { toValue: 0.5, ...PULSE }),
        Animated.timing(opacity, { toValue: 1, ...PULSE }),
      ])
    );
    pulse.start();
    return () => pulse.stop();
  }, [opacity, reduced]);

  return (
    <Animated.View
      aria-hidden
      style={[{ backgroundColor: colors.accent, borderRadius: radius.md, opacity }, style]}
      {...props}
    />
  );
}

export { Skeleton, type SkeletonProps };