StealThis .dev

React Native Skeleton Loader

A skeleton loading placeholder for React Native with shimmer animation, configurable shapes (text lines, circles, rectangles), and a card preset.

react-native typescript
Targets: React Native

Expo Snack

Code

import React, { useEffect, useRef } from "react";
import { Animated, StyleSheet, View, type DimensionValue, type ViewStyle } from "react-native";

/* ------------------------------------------------------------------ */
/*  Pulse hook                                                         */
/* ------------------------------------------------------------------ */

function usePulse(): Animated.Value {
  const anim = useRef(new Animated.Value(0.3)).current;

  useEffect(() => {
    const loop = Animated.loop(
      Animated.sequence([
        Animated.timing(anim, {
          toValue: 1,
          duration: 500,
          useNativeDriver: true,
        }),
        Animated.timing(anim, {
          toValue: 0.3,
          duration: 500,
          useNativeDriver: true,
        }),
      ])
    );
    loop.start();
    return () => loop.stop();
  }, [anim]);

  return anim;
}

/* ------------------------------------------------------------------ */
/*  Base skeleton shape                                                */
/* ------------------------------------------------------------------ */

interface BaseProps {
  style?: ViewStyle;
}

function SkeletonBase({ style }: BaseProps) {
  const opacity = usePulse();
  return <Animated.View style={[styles.base, style, { opacity }]} />;
}

/* ------------------------------------------------------------------ */
/*  Skeleton.Line                                                      */
/* ------------------------------------------------------------------ */

interface LineProps {
  width?: DimensionValue;
  height?: number;
}

function Line({ width = "100%", height = 16 }: LineProps) {
  return <SkeletonBase style={{ width, height, borderRadius: height / 2 }} />;
}

/* ------------------------------------------------------------------ */
/*  Skeleton.Circle                                                    */
/* ------------------------------------------------------------------ */

interface CircleProps {
  size?: number;
}

function Circle({ size = 48 }: CircleProps) {
  return <SkeletonBase style={{ width: size, height: size, borderRadius: size / 2 }} />;
}

/* ------------------------------------------------------------------ */
/*  Skeleton.Rect                                                      */
/* ------------------------------------------------------------------ */

interface RectProps {
  width?: DimensionValue;
  height?: number;
  borderRadius?: number;
}

function Rect({ width = "100%", height = 100, borderRadius = 8 }: RectProps) {
  return <SkeletonBase style={{ width, height, borderRadius }} />;
}

/* ------------------------------------------------------------------ */
/*  Skeleton.Card                                                      */
/* ------------------------------------------------------------------ */

function Card() {
  return (
    <View style={styles.card}>
      {/* Avatar row */}
      <View style={styles.cardRow}>
        <Circle size={44} />
        <View style={styles.cardMeta}>
          <Line width="50%" height={14} />
          <Line width="30%" height={12} />
        </View>
      </View>
      {/* Body lines */}
      <View style={styles.cardBody}>
        <Line width="100%" />
        <Line width="92%" />
        <Line width="60%" />
      </View>
    </View>
  );
}

/* ------------------------------------------------------------------ */
/*  Skeleton namespace                                                 */
/* ------------------------------------------------------------------ */

const Skeleton = {
  Line,
  Circle,
  Rect,
  Card,
};

/* ------------------------------------------------------------------ */
/*  Demo App                                                           */
/* ------------------------------------------------------------------ */

export default function App() {
  return (
    <View style={styles.container}>
      {/* Preset card */}
      <Skeleton.Card />

      <View style={styles.spacer} />

      {/* Custom skeleton layout */}
      <View style={styles.custom}>
        <Skeleton.Rect width={80} height={80} borderRadius={12} />
        <View style={styles.customLines}>
          <Skeleton.Line width="60%" height={14} />
          <Skeleton.Line width="90%" />
          <Skeleton.Line width="40%" height={12} />
        </View>
      </View>
    </View>
  );
}

/* ------------------------------------------------------------------ */
/*  Styles                                                             */
/* ------------------------------------------------------------------ */

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#0f172a",
    paddingHorizontal: 20,
    paddingTop: 64,
  },
  base: {
    backgroundColor: "#374151",
  },
  card: {
    backgroundColor: "#1e293b",
    borderRadius: 16,
    padding: 16,
  },
  cardRow: {
    flexDirection: "row",
    alignItems: "center",
    gap: 12,
  },
  cardMeta: {
    flex: 1,
    gap: 6,
  },
  cardBody: {
    marginTop: 16,
    gap: 8,
  },
  spacer: {
    height: 24,
  },
  custom: {
    flexDirection: "row",
    gap: 12,
    backgroundColor: "#1e293b",
    borderRadius: 16,
    padding: 16,
  },
  customLines: {
    flex: 1,
    gap: 8,
    justifyContent: "center",
  },
});

React Native Skeleton Loader

A lightweight skeleton placeholder system with a pulsing shimmer animation. Drop in pre-built shapes — lines, circles, and rectangles — or use the Skeleton.Card preset to instantly scaffold a social-media-style loading card.

Props

Skeleton.Line

PropTypeDefaultDescription
widthnumber | string100%Width of the line placeholder.
heightnumber16Height in pixels.

Skeleton.Circle

PropTypeDefaultDescription
sizenumber48Diameter of the circle.

Skeleton.Rect

PropTypeDefaultDescription
widthnumber | string100%Width of the rect.
heightnumber100Height in pixels.
borderRadiusnumber8Corner radius.

Skeleton.Card

No props — renders a ready-made social post placeholder (avatar circle, name line, and body lines).

Usage

{/* Ready-made card */}
<Skeleton.Card />

{/* Custom layout */}
<View style={{ flexDirection: "row", gap: 12 }}>
  <Skeleton.Rect width={80} height={80} borderRadius={12} />
  <View style={{ flex: 1, gap: 8 }}>
    <Skeleton.Line width="60%" />
    <Skeleton.Line width="90%" />
    <Skeleton.Line width="40%" />
  </View>
</View>

How it works

  1. A shared usePulse hook creates a looping Animated.Value that oscillates between 0.3 and 1 over 1 second using Animated.loop with Animated.sequence.
  2. Each shape component wraps its view in an Animated.View whose opacity is bound to the pulse value, producing the shimmer effect.
  3. All shapes use a dark gray background (#374151) so they blend naturally on dark-themed screens.