If you have ever tried to build a draggable home-screen style grid in React Native, you have probably discovered that FlatList with numColumns is not your friend. A FlatList is fundamentally a one-dimensional list: items flow in a fixed linear sequence and snap into fixed slots. You cannot freely place a tile at an arbitrary (row, column), you cannot mix tiles of different sizes naturally, and reordering "jumps" items between indices instead of gliding them across a plane. React Native Reshuffled (react-native-reshuffled) exists to fix exactly that gap.
Reshuffled is a high-performance, coordinate-based draggable 2D grid. Every item declares its own position and span, and when you drag one tile the others animate, or "reshuffle", to new coordinates along the shortest path, sliding across both axes rather than snapping between list indices. It is a great fit for app-launcher icon grids, dashboards with widgets of varying sizes, bento-grid editors, and even sliding-puzzle games.
Why Reshuffled Is Different
The headline trick is that Reshuffled is genuinely two-dimensional. Each item carries an explicit startRow and startColumn plus a width and height measured in grid cells. That means a 2x1 widget can sit happily next to four 1x1 icons, and the layout engine knows how to make room when you drag something into an occupied space.
The second differentiator is speed. The reshuffle algorithm, the part that recomputes the whole layout on every drag frame, is implemented as a C++ HybridObject using Nitro Modules (Marc Rousavy's react-native-nitro-modules). It runs over JSI with effectively zero bridge overhead, which is where the project's "quickest grid library" tagline comes from. When the native module is not available, for example on web, there is a pure-JS fallback so the grid still works, just a little slower.
Rounding out the feature set:
- True coordinate placement with arbitrary multi-cell spanning tiles.
- Smooth animated reshuffling driven by Reanimated and Gesture Handler.
- A pluggable algorithm via the
getNewGridprop, so you can implement game-like layouts (swaps, sliding puzzles) yourself. - Collision control through
allowCollisions, letting overlaps happen when you want them. - An
onDragEndcallback that hands you the full updated array so you can persist or sync the new arrangement.
A quick honesty note before we dive in: this is an early, lightly adopted library (version 0.2.3 at the time of writing, pre-1.0, with a single maintainer). It is a delight for demos, prototypes, and hobby projects, but vet it carefully before betting production on it.
Getting It Into Your Project
Reshuffled sits on top of a modern New Architecture stack, so you install it alongside its animation and native-module peers.
npm install react-native-reshuffled
npm install react-native-nitro-modules react-native-reanimated react-native-gesture-handler react-native-worklets
Or with Yarn:
yarn add react-native-reshuffled
yarn add react-native-nitro-modules react-native-reanimated react-native-gesture-handler react-native-worklets
On iOS, finish the native install:
cd ios && pod install
One thing to be clear-eyed about: this is a New-Architecture-only stack. It leans on Reanimated 4, react-native-worklets, and Nitro (which requires TurboModules and Fabric). Older apps still on the Paper architecture or Reanimated 2/3 are not the target audience here.
Your First Reshufflable Grid
Every item you hand to Reshuffled must extend its Cell shape, which describes both where the item sits and how big it is:
type Cell = {
id: string;
color: string;
height: number; // span in grid rows
width: number; // span in grid columns
startRow: number; // top-left row position
startColumn: number; // top-left column position
};
You are free to add any extra fields you like (a title, an icon, a payload), and items are matched internally by their id. Here is a minimal grid with one tile that spans two columns:
import React, { useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { Reshuffled } from 'react-native-reshuffled';
type Tile = {
id: string;
color: string;
title: string;
width: number;
height: number;
startColumn: number;
startRow: number;
};
const App = () => {
const [data, setData] = useState<Tile[]>([
{ id: '1', color: '#6200EE', title: 'A', width: 1, height: 1, startColumn: 0, startRow: 0 },
{ id: '2', color: '#03DAC6', title: 'B', width: 1, height: 1, startColumn: 1, startRow: 0 },
{ id: '3', color: '#B00020', title: 'C', width: 1, height: 1, startColumn: 0, startRow: 1 },
{ id: '4', color: '#FFDE03', title: 'D', width: 2, height: 1, startColumn: 0, startRow: 2 },
]);
return (
<View style={styles.container}>
<Reshuffled.Grid
data={data}
columns={2}
rows={3}
gapVertical={12}
gapHorizontal={12}
style={{}}
onDragEnd={(items) => setData(items)}
renderItem={({ item }) => (
<View style={[styles.card, { backgroundColor: item.color }]}>
<Text style={styles.cardText}>{item.title}</Text>
</View>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
card: { flex: 1, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
cardText: { color: 'white', fontWeight: '700', fontSize: 18 },
});
export default App;
Notice tile D with width: 2: that single multi-cell tile is the capability you simply cannot express with a FlatList. The grid measures its own pixel size via onLayout, derives cellWidth and cellHeight from your rows and columns, and lays everything out accordingly.
A Word on the style Prop
Even though the README treats style as optional, the source types it as required on the grid props. The safe move is to always pass at least an empty object (style={{}}) so TypeScript stays happy.
Reacting to Drops
The onDragEnd callback is how you keep your own state in sync with what the user just did. It fires after each successful drop and receives the full updated array, with every item carrying its freshly computed startRow and startColumn.
<Reshuffled.Grid
data={data}
columns={3}
rows={4}
style={{}}
onDragEnd={(items) => {
setData(items);
persistLayout(items); // save to storage or sync to a backend
}}
renderItem={renderTile}
/>
Despite the library's worklet-heavy marketing, onDragEnd itself runs on the regular JS thread. The gesture and animation work happens in worklets on the UI thread, and the committed result is marshaled back to JS (via scheduleOnRN from react-native-worklets) before the callback fires. The practical upshot is reassuring: you can call setState, write to async storage, or hit your API directly inside onDragEnd without any worklet ceremony.
One subtle gotcha: internally the dragged item gets moved to the end of the array on drop, so do not rely on array order being preserved. Always key and diff by id.
Showing a Drop Target Ghost
To give users a visual hint of where a tile will land, supply a renderShadow function. It renders a ghost or placeholder at the drop target while a drag is in progress:
<Reshuffled.Grid
data={data}
columns={3}
rows={4}
style={{}}
renderItem={renderTile}
renderShadow={({ item }) => (
<View style={[styles.card, styles.ghost, { borderColor: item.color }]} />
)}
/>
Going Further
Once you have the basics working, Reshuffled has a couple of levers that turn it from a tidy icon grid into a general-purpose layout engine.
Letting Tiles Overlap
By default, dropping a tile onto an occupied slot reshuffles everyone else out of the way, and dropping where there is no room snaps the dragged item back to its origin. Set allowCollisions to true and the rules change: dropping only moves the dragged item, overlaps are permitted, and the other items stay exactly where they are.
<Reshuffled.Grid
data={data}
columns={4}
rows={4}
style={{}}
allowCollisions
renderItem={renderTile}
/>
This is handy for free-form boards, mood boards, or any layout where you want the user, not the algorithm, to decide what sits on top of what.
Bringing Your Own Reshuffle Algorithm
The most powerful escape hatch is getNewGrid. By default Reshuffled uses its native C++ (or JS fallback) implementation to compute the live layout while you drag. You can replace that logic entirely with your own function, which is only consulted when allowCollisions is false.
import { Cell, GetNewGridProps } from 'react-native-reshuffled';
const swapNewGrid = ({
oldGrid,
pickedCellIndex,
targetRow,
targetCol,
rows,
columns,
movePenalty,
}: GetNewGridProps): Cell[] => {
const cells = [...oldGrid.cellsToBeSet];
const picked = cells[pickedCellIndex];
// Find whatever currently occupies the target slot and swap positions.
const targetIndex = cells.findIndex(
(c) => c.startRow === targetRow && c.startColumn === targetCol,
);
if (targetIndex !== -1 && targetIndex !== pickedCellIndex) {
const target = cells[targetIndex];
cells[targetIndex] = { ...target, startRow: picked.startRow, startColumn: picked.startColumn };
cells[pickedCellIndex] = { ...picked, startRow: targetRow, startColumn: targetCol };
}
return cells;
};
<Reshuffled.Grid
data={data}
columns={3}
rows={3}
style={{}}
getNewGrid={swapNewGrid}
renderItem={renderTile}
/>
This is exactly how the bundled example app builds a sliding-puzzle game on top of the same engine, complete with confetti when the board is solved. It demonstrates that Reshuffled is not just an icon-reordering widget but a generic substrate for grid-based games and editors.
There is also an experimental movePenalty prop (a number, defaulting to 0) that tunes how costly the algorithm considers moving items. It is undocumented in the README and comes with a warning baked into the source: higher values make the algorithm slower. Reach for it only when you need to nudge the reshuffle behavior and are willing to profile the result.
How It All Fits Together
Under the hood, the pieces line up cleanly. Each tile is a draggable rectangle driven by a Gesture Handler Gesture.Pan() inside a GestureDetector, animated with Reanimated shared values and useAnimatedStyle. Movement to a new position animates with withTiming over roughly 300ms, while snap-backs are a touch quicker at around 200ms. On every drag update, the dragged item's pixel position is rounded to a target (row, col), the reshuffle algorithm (native or JS) computes the new layout, and the component diffs by id and glides each moved tile to its new home. The whole thing also recalculates when the container resizes or the device rotates, so your grid stays responsive.
When to Pick It, and When Not To
Reshuffled's sweet spot is clear: you need true 2D placement with differently sized tiles and an animated reshuffle, and you are already on the New Architecture. If you only need to sort a grid of equally sized cells, the more mature and widely adopted react-native-sortables is a safer bet today. If you want low-level drag-and-drop primitives to compose yourself, something like react-native-reanimated-dnd gives you zones and droppables to build from. And if you just need a draggable single-column list, react-native-draggable-flatlist solves a different, simpler problem.
Where Reshuffled earns its place is the combination it alone offers: explicit coordinate placement, arbitrary multi-cell spans, a collision toggle, a swappable algorithm, and a native C++ engine for the layout math. For home-screen launchers, widget dashboards, bento editors, and puzzle games, that is a genuinely expressive toolkit.
Keep the caveats in mind. It is pre-1.0 and lightly adopted, web support is partial and runs the slower JS fallback, and the Cell type insists on a color field whether you use it or not. But as a well-architected, native-accelerated answer to a problem FlatList was never built to solve, React Native Reshuffled is a small library worth keeping on your radar, and a genuinely fun one to play with.