Masonry Layout allows you to create a grid of items with different heights, perfect for displaying collections like images, cards, or mixed content. Think Pinterest-style layouts where items flow naturally into columns.
Basic Masonry Layout
Enable masonry layout by adding the masonry prop to your FlashList:
import React from "react" ;
import { View , Text , StyleSheet } from "react-native" ;
import { FlashList } from "@shopify/flash-list" ;
interface MasonryItem {
id : string ;
title : string ;
height : number ;
}
const MyMasonryList = () => {
const data : MasonryItem [] = [
{ id: "1" , title: "Item 1" , height: 150 },
{ id: "2" , title: "Item 2" , height: 200 },
{ id: "3" , title: "Item 3" , height: 120 },
// ... more items
];
return (
< FlashList
data = { data }
masonry
numColumns = { 2 }
renderItem = { ({ item }) => (
< View style = { [ styles . card , { height: item . height }] } >
< Text > { item . title } </ Text >
</ View >
) }
keyExtractor = { ( item ) => item . id }
/>
);
};
const styles = StyleSheet . create ({
card: {
backgroundColor: "#f0f0f0" ,
margin: 4 ,
borderRadius: 8 ,
padding: 12 ,
},
});
The masonry prop works with numColumns to determine how many columns your layout should have.
Custom Column Spans
For more complex layouts where items can span multiple columns, use overrideItemLayout:
Define your data structure
Add a span property to your items to control how many columns they occupy: interface MasonryItem {
id : string ;
title : string ;
imageUrl : string ;
height : number ;
span : number ; // Number of columns this item spans
}
Configure overrideItemLayout
Use overrideItemLayout to set the span for each item: < FlashList
data = { data }
masonry
numColumns = { 3 }
overrideItemLayout = { ( layout , item ) => {
// Set custom span based on item data
layout . span = item . span ;
// Heights are automatically determined by rendered content
} }
renderItem = { ({ item }) => (
< View style = { styles . card } >
< Image
source = { { uri: item . imageUrl } }
style = { { width: "100%" , height: item . height } }
/>
< Text > { item . title } </ Text >
</ View >
) }
keyExtractor = { ( item ) => item . id }
/>
Style your items
Items automatically adjust their width based on the span value: const styles = StyleSheet . create ({
card: {
backgroundColor: "white" ,
margin: 4 ,
borderRadius: 12 ,
overflow: "hidden" ,
shadowColor: "#000" ,
shadowOffset: { width: 0 , height: 2 },
shadowOpacity: 0.1 ,
shadowRadius: 4 ,
elevation: 3 ,
},
});
Advanced: Interactive Masonry
Create dynamic masonry layouts with expandable items:
import React , { useState , useCallback } from "react" ;
import { View , Text , TouchableOpacity , Image } from "react-native" ;
import { FlashList } from "@shopify/flash-list" ;
interface MasonryItem {
id : string ;
title : string ;
imageUrl : string ;
height : number ;
span : number ;
isExpanded : boolean ;
}
const InteractiveMasonry = () => {
const [ items , setItems ] = useState < MasonryItem []>([
{ id: "1" , title: "Photo 1" , imageUrl: "..." , height: 200 , span: 1 , isExpanded: false },
{ id: "2" , title: "Photo 2" , imageUrl: "..." , height: 250 , span: 2 , isExpanded: false },
// ... more items
]);
const handleToggleExpand = useCallback (( id : string ) => {
setItems (( currentItems ) =>
currentItems . map (( item ) =>
item . id === id ? { ... item , isExpanded: ! item . isExpanded } : item
)
);
}, []);
return (
< FlashList
data = { items }
masonry
optimizeItemArrangement
numColumns = { 3 }
overrideItemLayout = { ( layout , item ) => {
layout . span = item . span ;
// Adjust height based on expanded state
layout . size = item . isExpanded ? item . height * 1.5 : item . height ;
} }
renderItem = { ({ item }) => (
< TouchableOpacity
activeOpacity = { 0.9 }
onPress = { () => handleToggleExpand ( item . id ) }
>
< View
style = { {
height: item . isExpanded ? item . height * 1.5 : item . height ,
backgroundColor: "#f0f0f0" ,
margin: 4 ,
borderRadius: 12 ,
overflow: "hidden" ,
} }
>
< Image
source = { { uri: item . imageUrl } }
style = { { width: "100%" , height: "100%" } }
resizeMode = "cover"
/>
< View style = { { position: "absolute" , bottom: 8 , left: 8 } } >
< Text style = { { color: "white" , fontWeight: "bold" } } >
{ item . title }
</ Text >
< Text style = { { color: "white" , fontSize: 12 } } >
{ item . isExpanded ? "Tap to shrink" : "Tap to expand" }
</ Text >
</ View >
</ View >
</ TouchableOpacity >
) }
keyExtractor = { ( item ) => item . id }
/>
);
};
Configuration Options
optimizeItemArrangement
When enabled, FlashList attempts to minimize column height differences by adjusting item placement:
< FlashList
masonry
optimizeItemArrangement = { true } // Default: true
numColumns = { 3 }
// ...
/>
Enabled (Default)
Disabled
Items are arranged to balance column heights, which may change the order slightly but creates a more visually balanced layout.
Items maintain their original order strictly, which may result in uneven column heights.
Migration from v1
If you’re upgrading from FlashList v1, the API has been simplified in v2.
Before (v1)
import { MasonryFlashList } from "@shopify/flash-list" ;
< MasonryFlashList
data = { data }
numColumns = { 2 }
overrideItemLayout = { ( layout , item , index , maxColumns , extraData ) => {
layout . span = item . span ;
layout . size = item . height ; // Required size estimate
} }
renderItem = { renderItem }
/>
After (v2)
import { FlashList } from "@shopify/flash-list" ;
< FlashList
data = { data }
masonry // Just add this prop!
numColumns = { 2 }
overrideItemLayout = { ( layout , item ) => {
layout . span = item . span ;
// Size is automatically determined - no estimates needed!
} }
renderItem = { renderItem }
/>
Key Changes
Simplified API Use FlashList with masonry prop instead of separate MasonryFlashList component
Automatic Heights No need to provide size estimates in overrideItemLayout - heights are determined automatically
No getColumnFlex The getColumnFlex prop is no longer supported in v2
Better Performance Improved layout calculation and rendering performance
Best Practices
Use keyExtractor for stable keys
Use image libraries with caching for better performance: import FastImage from 'react-native-fast-image' ;
< FastImage
source = { { uri: item . imageUrl } }
style = { { width: "100%" , height: item . height } }
resizeMode = "cover"
/>
Create visual interest by using span patterns: const span = index === 0 || index % 7 === 0 ? 2 : 1 ;
Use margins and padding for visual separation: contentContainerStyle = {{ paddingHorizontal : 4 , paddingVertical : 8 }}
Common Issues
Items overlapping or misaligned? Make sure your item heights are set correctly in both the style and the data.
Layout jumping during scroll? This is usually caused by inconsistent item heights. Ensure your height calculations are deterministic.
Performance Tips Learn how to optimize your masonry list
Grid Layouts Create uniform grid layouts