About this animation
A pull-to-refresh feed with shared shimmer and staggered post reveals, with configurable posts, labels, colors, and font.
How it works
- A downward pan applies half-speed resistance and grows a thin refresh arc below the search header.
- Releasing past 72 points starts a spring and rotates the arc while skeleton posts return from bottom to top.
- A shared shimmer phase offsets each post by 80 milliseconds while keeping every bar in that post in sync.
- The content reveal travels top to bottom, opening avatars and resolving author details before the post copy.
- A new posts pill returns to the top on tap; each post has an independent like pop and the bottom tab marker slides.
Install
npx expo install @expo/vector-icons expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsUsage
import { useRef } from 'react';
import {
SocialFeedSkeleton,
type SocialFeedPost,
type SocialFeedSkeletonRef,
} from './SocialFeedSkeleton';
const posts: SocialFeedPost[] = [
{
id: 'maya',
author: 'Maya Ortiz',
role: 'Product Designer',
avatar: require('./maya.jpg'),
time: '2h',
text: 'Small details make a big difference. We made the daily flow simpler.',
likes: 128,
engagement: '12 comments · 3 reposts',
},
];
export function Feed() {
const feed = useRef<SocialFeedSkeletonRef>(null);
return (
<SocialFeedSkeleton
ref={feed}
posts={posts}
newPosts={3}
onRefresh={async () => fetch('/feed')}
onLike={(id) => console.info('Liked', id)}
/>
);
}To control loading from the parent, pass loading. Otherwise the component runs its initial load and owns the refresh state. Set autoplay for the repeating demo sequence. Call feed.current?.refresh() or feed.current?.scrollToTop() from a parent control.
Props
| Name | Type | Default | Description |
|---|---|---|---|
posts | SocialFeedPost[] | [] | Post data including local or remote images. |
headerAvatar | ImageSourcePropType | First post avatar | Current user portrait in the header. |
newPostAvatars | ImageSourcePropType[] | [] | Portraits in the new posts pill. |
newPosts | number | 3 | Count in the new posts pill. |
labels | Partial<SocialFeedLabels> | English labels | Search, tab, like, and refresh labels. |
colors | Partial<SocialFeedColors> | Light palette | Full theme colors for surface, text, skeleton, separators, and accent. |
loading | boolean | Uncontrolled | Optional externally controlled loading state. |
onRefresh | () => void | Promise<void> | — | Called after a pull or imperative refresh. |
onLike | (id: string) => void | — | Called when a post is liked or unliked. |
autoplay | boolean | false | Runs the refresh, reveal, new posts, and like sequence about every 10 seconds. |
fontFamily | string | System | Font for feed labels and post text. |
style | StyleProp<ViewStyle> | — | Root container style. |
testID | string | social-feed-skeleton | Root and child test ID prefix. |
SocialFeedPost contains id, author, role, optional avatar, time, text, optional image, and likes. Pass engagement for a localized comment and repost summary. Organizations can supply avatarMonogram and avatarColor instead of a photo. The engagement and monogram palette tokens control summary and initial text colors. The ref exposes refresh(), scrollToTop(), and like(id) for scripted demos.
Compatibility
- Expo SDK 58, React Native 0.88, Reanimated 4, Gesture Handler 3
- iOS, Android, and web
- Expo Go compatible; no extra native module is required
- Icons: Feather from
@expo/vector-icons
Accessibility
Interactive controls have button or tab roles and labels. The pull gesture has a labeled refresh indicator. Reduced motion shortens the content reveal and softens shimmer movement. The scroll view and tab controls remain available to touch and keyboard users.
Why this one
The defining motion belongs to each post: all of its skeleton bars share one soft sweep, while neighboring posts follow 80 milliseconds apart. A completed refresh reverses that rhythm into a top-down avatar, author, and copy reveal.





