react-native-image-collage: Build Facebook-Style Photo Grids in React Native
A complete guide to building Facebook and Instagram-style image collage layouts in React Native using the react-native-image-collage npm package — automatic grids, +N overflow, full-screen viewer, Expo support, and TypeScript.
Faisal Khawaj
Author
If you have ever built a social feed, a chat UI with photo attachments, or a gallery screen in React Native, you know the problem: laying out multiple images cleanly is tedious. One image is easy. Two side by side is manageable. Three, four, or five with a +N overflow badge on the last tile — that's where things get messy fast.
react-native-image-collage is an open-source npm package I built and maintain that handles this automatically. Drop in an array of image URLs, and the component picks the right layout — no manual flex math needed.
This guide walks through how it works, how to set it up, and how to use every feature — from basic grids to pinch-to-zoom full-screen viewers with Expo.
What it does
The package gives you Facebook and Instagram-style collage layouts that auto-select based on how many images you pass:
| Images | Layout |
|---|---|
| 1 | Full width |
| 2 | Side by side |
| 3 | Large left, two stacked right |
| 4 | 2×2 grid |
| 5+ | Grid + +N overlay on the last tile |
Width comes from the parent container automatically via onLayout. Height is derived from container width and image aspect ratios. You don't need to hardcode dimensions.
Three entry points cover different needs:
| Entry point | Requires | Use when |
|---|---|---|
react-native-image-collage | — | Default — RN CLI or Expo, no extras |
react-native-image-collage/viewer | react-native-image-viewing | Full-screen viewer, pinch/pan, double-tap |
react-native-image-collage/expo | expo-image | Blurhash, advanced caching, pinch zoom |
Install
npm install react-native-image-collage
# or
yarn add react-native-image-collageFor Expo apps (recommended — uses expo-image for blurhash and caching):
npx expo install react-native-image-collage expo-imageFor the full-screen viewer (RN CLI — pinch / pan / double-tap):
npm install react-native-image-viewingNo native linking required. The package uses only React Native core APIs.
Quick start
import { ImageCollage } from "react-native-image-collage";
export default function FeedPost() {
const images = [
{ uri: "https://picsum.photos/seed/a/900/600", aspectRatio: 1.5 },
{ uri: "https://picsum.photos/seed/b/900/700", aspectRatio: 1.29 },
{ uri: "https://picsum.photos/seed/c/900/900", aspectRatio: 1 },
{ uri: "https://picsum.photos/seed/d/900/500", aspectRatio: 1.8 },
];
return (
<ImageCollage
images={images}
spacing={4}
borderRadius={12}
onImagePress={(index) => console.log("Tapped image", index)}
/>
);
}Four images → automatic 2×2 grid. Change the array length and the layout adjusts itself.
The images prop accepts three formats
You can pass images in whichever format suits your data:
// 1. Plain URL string
images={["https://example.com/photo.jpg"]}
// 2. React Native ImageSource object
images={[require("./assets/photo.jpg")]}
// 3. Object with optional aspect ratio (best performance — no async measurement)
images={[{ uri: "https://example.com/photo.jpg", aspectRatio: 1.5 }]}When aspectRatio is omitted, the component measures it from the image before layout. Providing it upfront avoids a layout shift on first render.
Core props
| Prop | Type | Default | Description |
|---|---|---|---|
images | CollageImageInput[] | required | URLs, sources, or { uri, aspectRatio } |
spacing | number | 6 | Gap between tiles in pixels |
borderRadius | number | 12 | Corner radius on each tile |
maxVisibleImages | number | 4 | Max tiles before +N overflow |
layoutMinHeight | number | 200 | Min layout height |
layoutMaxHeight | number | 520 | Max layout height |
onImagePress | (index) => void | — | Tap handler per tile |
placeholderColor | string | #E8E8E8 | Loading tile background |
renderImage | CollageImageRenderer | RN Image | Swap in a custom renderer |
Controlling overflow
By default, up to 4 tiles are visible. When you have more images, the last tile shows a +N badge.
// 6 images, only 3 visible → third tile shows "+3"
<ImageCollage
images={sixPhotos}
maxVisibleImages={3}
onImagePress={(index) => openViewer(index)}
/>The +N count is calculated automatically from images.length - maxVisibleImages.
Adding a full-screen viewer (RN CLI)
The /viewer entry wraps the collage with react-native-image-viewing — a popular swipeable, pinch-to-zoom full-screen modal.
import { ImageCollageWithViewer } from "react-native-image-collage/viewer";
export default function PhotoPost() {
return (
<ImageCollageWithViewer
images={photos}
spacing={4}
borderRadius={10}
viewerProps={{
pinchToZoomEnabled: true,
doubleTapToZoomEnabled: true,
swipeToCloseEnabled: true,
}}
/>
);
}Tapping any tile opens the full-screen viewer at the correct image index. Swipe down to close.
Expo entry — blurhash, caching, and pinch zoom
If your app uses Expo, the /expo entry uses expo-image as the renderer. You get blurhash placeholders, better caching, and built-in pinch-to-zoom without needing react-native-image-viewing.
import { ImageCollageWithViewer } from "react-native-image-collage/expo";
export default function GalleryPost() {
return (
<ImageCollageWithViewer
images={photos}
spacing={4}
borderRadius={12}
blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
prioritizeFirstImage
viewerProps={{
pinchToZoomEnabled: true,
doubleTapToZoomEnabled: true,
minScale: 1,
maxScale: 3,
doubleTapScale: 2.5,
}}
/>
);
}prioritizeFirstImage tells expo-image to load the first tile at high priority and the rest at normal priority — a good default for feed posts where the first image is most important.
Using a custom image renderer
The renderImage prop lets you swap in any image component. This is useful if you want to use FastImage, a custom shimmer, or a CDN-optimized image component.
import FastImage from "react-native-fast-image";
<ImageCollage
images={photos}
renderImage={({ source, style, priority }) => (
<FastImage
source={source as FastImage.ImageSource}
style={style}
resizeMode="cover"
priority={priority === "high" ? FastImage.priority.high : FastImage.priority.normal}
/>
)}
/>Real-world example: social feed post
Here is what a full feed post component looks like in practice:
import React from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import { ImageCollageWithViewer } from "react-native-image-collage/viewer";
type FeedPost = {
author: string;
avatar: string;
body: string;
images: string[];
timestamp: string;
};
export function FeedPostCard({ post }: { post: FeedPost }) {
return (
<View style={styles.card}>
<View style={styles.header}>
<Image source={{ uri: post.avatar }} style={styles.avatar} />
<View>
<Text style={styles.author}>{post.author}</Text>
<Text style={styles.time}>{post.timestamp}</Text>
</View>
</View>
<Text style={styles.body}>{post.body}</Text>
{post.images.length > 0 && (
<ImageCollageWithViewer
images={post.images}
spacing={3}
borderRadius={8}
maxVisibleImages={4}
viewerProps={{ pinchToZoomEnabled: true }}
/>
)}
</View>
);
}
const styles = StyleSheet.create({
card: { backgroundColor: "#fff", borderRadius: 12, padding: 14, marginBottom: 12 },
header: { flexDirection: "row", alignItems: "center", gap: 10, marginBottom: 10 },
avatar: { width: 38, height: 38, borderRadius: 19 },
author: { fontWeight: "700", fontSize: 15 },
time: { color: "#888", fontSize: 12 },
body: { fontSize: 15, lineHeight: 22, marginBottom: 10 },
});Handling chat attachments
For a chat UI where messages may have one, two, or multiple images:
import { ImageCollage } from "react-native-image-collage";
function ChatBubbleImages({ uris }: { uris: string[] }) {
if (uris.length === 0) return null;
return (
<View style={{ maxWidth: 260 }}>
<ImageCollage
images={uris}
spacing={2}
borderRadius={10}
maxVisibleImages={4}
layoutMaxHeight={300}
onImagePress={(i) => openLightbox(uris, i)}
/>
</View>
);
}The component width follows the parent View width — for a chat bubble that constrains itself to maxWidth: 260, the collage fills that space correctly.
TypeScript types
The package ships full TypeScript definitions. Key types:
import type {
CollageImageInput, // string | ImageSourcePropType | { uri, aspectRatio? }
ImageCollageProps, // full props interface for <ImageCollage>
CollageImageRenderer, // (props: CollageImageRenderProps) => ReactElement
CollageViewerRenderer, // (props: CollageViewerRenderProps) => ReactElement | null
} from "react-native-image-collage";Compatibility
| React Native | 0.72+ (including 0.86+ / Expo SDK 57) |
| React | 18+ / 19+ |
| Expo | Optional — use /expo entry with expo-image |
| Viewer | Optional — use /viewer entry with react-native-image-viewing |
| TypeScript | Full types included |
FAQ
Does react-native-image-collage work with Expo?
Yes. You can use the base react-native-image-collage import in any Expo project. For blurhash placeholders and better caching, use the /expo entry with expo-image installed via npx expo install expo-image.
Does it need native linking?
No. The package uses only React Native core APIs — View, Image, Pressable, onLayout. No native modules and no pod install changes. The optional /expo entry uses expo-image, which Expo manages automatically.
How does the +N overflow work?
Set maxVisibleImages={3} with 6 images — you get a 3-tile layout where the third tile shows the last image dimmed with a +3 text overlay. Tapping it triggers onImagePress at that index so you can open a viewer at the right position.
Can I use a custom image component like FastImage?
Yes — pass a renderImage function. It receives { source, style, priority, transition } and you return any image element.
What happens with a single image?
A single image renders full width at a height derived from its aspect ratio, clamped between layoutMinHeight and layoutMaxHeight.
Links
Have questions or a bug to report? Open an issue on GitHub.
Building a social or chat app in React Native? Book a 15-min call or start a project — I ship production React Native apps for iOS and Android.
Keep reading