react-native-drag-drop-board
v1.0.7
Published
A lightweight, reusable React Native drag-and-drop matching board powered by Gesture Handler and Reanimated.
Maintainers
Readme
react-native-drag-drop-board
A reusable, TypeScript-first React Native drag-and-drop matching board for educational activities, games, onboarding flows, quizzes, and custom matching interactions.
Demo
Note: GitHub and npm's README renderers strip
<video>tags for security reasons, so the demo won't play inline there — viewers will just see the link. If you want it to play inline on npm/GitHub, convert the clip to a.gifand reference it with a normal<img>tag instead.
Features
- Two-column drop-zone and draggable-tile layout
- Smooth spring-based drag, snap, and bounce animations
- Configurable snap distance
- Match state support through
matchedIds - Reset support through
resetKey - Fully custom React content for zones and draggable tiles
- Custom draggable tile colors
- TypeScript types included
- Built with
react-native-gesture-handler - Built with
react-native-reanimated - Application-specific state remains outside the component
- Suitable for games, educational activities, quizzes, onboarding, and matching experiences
Installation
Install the package together with its peer dependencies:
npm install react-native-drag-drop-board react-native-gesture-handler react-native-reanimatedOr:
yarn add react-native-drag-drop-board react-native-gesture-handler react-native-reanimatedPeer dependencies
This package requires:
react-native-gesture-handlerreact-native-reanimated
Follow the official setup instructions for those libraries for the React Native version used by your application.
Quick Start
import React, { useCallback, useState } from 'react';
import { Text, View } from 'react-native';
import DragDropBoard, {
DragDropItem,
} from 'react-native-drag-drop-board';
const ITEMS: DragDropItem[] = [
{
id: 'a',
color: '#FF6B6B',
zoneContent: <Text>A</Text>,
dragContent: (
<Text
style={{
color: '#fff',
fontWeight: '800',
fontSize: 28,
}}
>
A
</Text>
),
},
{
id: 'b',
color: '#4ECDC4',
zoneContent: <Text>B</Text>,
dragContent: (
<Text
style={{
color: '#fff',
fontWeight: '800',
fontSize: 28,
}}
>
B
</Text>
),
},
];
export default function Example() {
const [matchedIds, setMatchedIds] = useState<Set<string>>(new Set());
const [resetKey, setResetKey] = useState(0);
const handleMatch = useCallback((id: string) => {
setMatchedIds((current) => new Set(current).add(id));
}, []);
return (
<View style={{ flex: 1 }}>
<DragDropBoard
items={ITEMS}
matchedIds={matchedIds}
onMatch={handleMatch}
resetKey={resetKey}
snapDistance={100}
/>
</View>
);
}How It Works
Each item represents one matching pair.
The zoneContent is rendered in the left drop zone, while dragContent is rendered as the draggable tile on the right.
When a tile is released close enough to its matching zone:
- The board calculates the distance between the tile and its matching zone.
- If the distance is within
snapDistance, the tile springs into the zone. onMatch(id)is called.- The application can add the ID to
matchedIds. - The matched tile becomes disabled and visually faded.
If the tile is released outside the configured snap distance, it springs back to its original position.
API
DragDropBoardProps
| Prop | Type | Default | Description |
|---|---|---:|---|
| items | DragDropItem[] | Required | Matching pairs and their rendered content. |
| onMatch | (id: string) => void | Required | Called when a tile successfully snaps into its matching zone. |
| matchedIds | ReadonlySet<string> | Empty set | IDs that are already matched. Matched tiles are disabled and faded. |
| resetKey | number \| string | 0 | Change this value to return all draggable tiles to their original positions. |
| snapDistance | number | 100 | Maximum distance in pixels for a successful match. |
| snapSpring | { damping: number; stiffness: number } | { damping: 14, stiffness: 140 } | Spring configuration used when a tile successfully snaps. |
| bounceSpring | { damping: number; stiffness: number } | { damping: 9, stiffness: 160 } | Spring configuration used when a tile misses its matching zone. |
| style | StyleProp<ViewStyle> | — | Style applied to the root gesture-handler container. |
DragDropItem
export interface DragDropItem {
id: string;
zoneContent: React.ReactNode;
dragContent: React.ReactNode;
color?: string;
}Properties
id
Unique identifier for the matching pair.
id: 'apple'The same ID is used to associate the draggable tile with its drop zone.
zoneContent
React content displayed inside the matching drop zone.
zoneContent={<Text>🍎</Text>}dragContent
React content displayed inside the draggable tile.
dragContent={<Text>Apple</Text>}color
Optional background color for the draggable tile.
color: '#FF6B6B'Matching State
The board does not own your application matching state.
Your screen controls matchedIds:
const [matchedIds, setMatchedIds] = useState<Set<string>>(new Set());
const handleMatch = useCallback((id: string) => {
setMatchedIds((current) => {
const next = new Set(current);
next.add(id);
return next;
});
}, []);Then:
<DragDropBoard
items={ITEMS}
matchedIds={matchedIds}
onMatch={handleMatch}
/>This makes it easy to connect the board to:
- Redux
- Zustand
- Context
- local React state
- game progress
- analytics
- sound effects
- scoring systems
Reset the Board
Use resetKey when you want all draggable tiles to return to their original positions.
const [resetKey, setResetKey] = useState(0);
const resetGame = () => {
setMatchedIds(new Set());
setResetKey((current) => current + 1);
};Then:
<DragDropBoard
items={ITEMS}
matchedIds={matchedIds}
onMatch={handleMatch}
resetKey={resetKey}
/>Every time resetKey changes, the draggable tiles reset.
Custom Content
The board accepts any React content.
Images
{
id: 'cat',
color: '#FF6B6B',
zoneContent: (
<Image
source={require('./assets/cat.png')}
style={{ width: 70, height: 70 }}
/>
),
dragContent: (
<Text style={{ color: '#fff', fontSize: 24 }}>
CAT
</Text>
),
}Icons
{
id: 'home',
zoneContent: <HomeIcon size={48} />,
dragContent: <Text>HOME</Text>,
}Custom components
{
id: 'one',
zoneContent: <MyDropZone />,
dragContent: <MyDraggableTile />,
}Animation Configuration
Snap animation
<DragDropBoard
items={ITEMS}
onMatch={handleMatch}
snapSpring={{
damping: 14,
stiffness: 140,
}}
/>Failed-drop animation
<DragDropBoard
items={ITEMS}
onMatch={handleMatch}
bounceSpring={{
damping: 9,
stiffness: 160,
}}
/>Snap distance
<DragDropBoard
items={ITEMS}
onMatch={handleMatch}
snapDistance={120}
/>A larger value makes matching more forgiving.
Architecture
The package intentionally keeps application-specific data out of the component.
Application owns
- Matching state
- Reset state
- Game progress
- Scores
- Sounds
- Analytics
- Content
- Navigation
- Business rules
Board owns
- Gesture handling
- Drag movement
- Layout measurement
- Drop-distance calculation
- Snap animation
- Bounce-back animation
- Rendering of zones and draggable tiles
This keeps the component reusable across different applications and use cases.
Example Use Cases
Educational apps
- Match letters with pictures
- Match numbers with quantities
- Match animals with sounds
- Match words with images
- Match shapes with names
Games
- Memory matching
- Puzzle interactions
- Sorting games
- Learning games
- Drag-and-drop challenges
Product / onboarding experiences
- Feature matching
- Preference selection
- Interactive tutorials
- Guided onboarding
- Categorization experiences
Troubleshooting
Dragging does not work
Make sure your application is correctly configured for react-native-gesture-handler.
For applications where the gesture-handler root is required, make sure the relevant screen or application root is wrapped appropriately according to your Gesture Handler version.
Reanimated errors
Make sure react-native-reanimated is correctly installed and configured for your React Native version.
After changing native dependencies, rebuild the application.
For iOS:
cd ios
pod install
cd ..Then rebuild the app.
Demo video is not showing on npm or GitHub
Both renderers strip raw <video> tags for security reasons, so:
- Convert
DEMO.movto a.gif(e.g. withffmpegor a tool like Gifski). - Reference the
.gifwith a plain<img>tag instead of<video>. - Keep the file under a few MB — large GIFs load slowly or get truncated by npm's README size limit.
License
MIT © shar25111996
