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/skeletonThe 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.
| Prop | Type | Default |
|---|---|---|
ref | Ref<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 GitHubimport { 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 };