A highly customizable, performant, and visually appealing wheel of fortune component for React Native applications. Perfect for games, promotions, and interactive experiences.
- 🎨 Fully Customizable - Colors, sizes, text, and more
- 🎯 Flexible Winner Selection - Probability-based OR predetermined winner
- 🎮 External Control - Trigger spins from external buttons via ref
- 💡 Decorative Boundary - Optional lights/boundary around wheel
- 📱 Cross-Platform - Works on iOS, Android, and Web (via Expo)
- ⚡ Performant - Smooth 60 FPS animations using native driver
- 📦 Lightweight - Minimal dependencies
- 🎭 TypeScript Support - Full type definitions included
npm install react-native-prize-wheel
# or
yarn add react-native-prize-wheelThis package requires react-native-svg:
npm install react-native-svg
# or
yarn add react-native-svgFor Expo projects:
expo install react-native-svgimport React from 'react';
import { View } from 'react-native';
import WheelOfFortune from 'react-native-prize-wheel';
const rewards = [
{ id: 1, label: '100 Coins', value: 100, color: '#FFD700' },
{ id: 2, label: '50 Coins', value: 50, color: '#C0C0C0' },
{ id: 3, label: '200 Coins', value: 200, color: '#FF6B6B' },
{ id: 4, label: '10 Coins', value: 10, color: '#4ECDC4' },
{ id: 5, label: '500 Coins', value: 500, color: '#95E1D3' },
{ id: 6, label: 'Try Again', value: 0, color: '#F38181' },
];
function App() {
const handleSpinEnd = (reward) => {
console.log('Won:', reward);
alert(`You won ${reward.label}!`);
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<WheelOfFortune
rewards={rewards}
onSpinEnd={handleSpinEnd}
/>
</View>
);
}
export default App;const rewards = [
{ id: 1, label: '100 Coins', value: 100, probability: 2 }, // 2x more likely
{ id: 2, label: '50 Coins', value: 50, probability: 3 }, // 3x more likely
{ id: 3, label: '500 Coins', value: 500, probability: 0.5 }, // Rare (0.5x)
{ id: 4, label: '10 Coins', value: 10, probability: 4 }, // 4x more likely
];
<WheelOfFortune
rewards={rewards}
onSpinEnd={handleSpinEnd}
/>The wheel automatically fills its parent container width and maintains a 1:1 aspect ratio:
<View style={{ width: '80%' }}>
<WheelOfFortune
rewards={rewards}
onSpinEnd={handleSpinEnd}
// size="auto" is default - fills parent width with square aspect ratio
// maxSize={400} is default - prevents wheel from being too large
/>
</View>Or set a fixed size:
<WheelOfFortune
rewards={rewards}
onSpinEnd={handleSpinEnd}
size={300} // Fixed 300x300 pixels
/>const [winnerIndex, setWinnerIndex] = useState(null);
// Fetch winner from backend
const fetchWinner = async () => {
const response = await api.getSpinWinner();
setWinnerIndex(response.winnerIndex); // 0 = first reward, 1 = second, etc.
};
<WheelOfFortune
rewards={rewards}
onSpinEnd={handleSpinEnd}
winnerIndex={winnerIndex} // Overrides probability
/>import { useRef } from 'react';
import { TouchableOpacity, Text } from 'react-native';
const wheelRef = useRef(null);
<WheelOfFortune
ref={wheelRef}
rewards={rewards}
onSpinEnd={handleSpinEnd}
tapToSpin={false} // Disable tap-to-spin, use external button
/>
<TouchableOpacity onPress={() => wheelRef.current?.spin()}>
<Text>Spin the Wheel!</Text>
</TouchableOpacity><WheelOfFortune
rewards={rewards}
onSpinEnd={handleSpinEnd}
size={400}
borderWidth={15}
borderColor="#2C3E50"
textSize={18}
textColor="#FFFFFF"
duration={5000}
centerBackgroundColor="#E74C3C"
centerTextColor="#FFFFFF"
showBoundary={true}
lightCount={24}
lightColor="#FFD700"
/><WheelOfFortune
rewards={rewards}
onSpinEnd={(reward) => {
console.log('Spin ended:', reward);
// Update user balance, show modal, etc.
}}
onSpinStart={() => {
console.log('Spin started!');
// Disable other UI elements, play sound, etc.
}}
onSpinning={(angle) => {
console.log('Current angle:', angle);
// Track spinning progress
}}
/>| Prop | Type | Description |
|---|---|---|
rewards |
Reward[] |
Array of reward objects |
onSpinEnd |
(reward: Reward) => void |
Callback when spin completes |
| Prop | Type | Default | Description |
|---|---|---|---|
winnerIndex |
number | null |
null |
Predetermined winner index (0-based). Overrides probability when set. |
| Prop | Type | Default | Description |
|---|---|---|---|
size |
number | 'auto' |
'auto' |
Wheel diameter in pixels, or 'auto' to fill parent width (maintains 1:1 aspect ratio) |
maxSize |
number |
400 |
Maximum size when using size='auto' |
borderWidth |
number |
0 |
Border thickness around wheel |
borderColor |
string |
'#1A1A1A' |
Border color around wheel |
textColor |
string |
'#000000' |
Default text color for segments |
textSize |
number |
16 |
Font size for segment text |
| Prop | Type | Default | Description |
|---|---|---|---|
duration |
number |
4000 |
Spin duration in milliseconds |
disabled |
boolean |
false |
Disable spinning |
| Prop | Type | Default | Description |
|---|---|---|---|
showBoundary |
boolean |
true |
Show decorative boundary with lights |
lightCount |
number |
20 |
Number of lights around boundary |
lightSize |
number |
6 |
Size of each light |
boundaryColor |
string |
'#2D3748' |
Color of boundary ring |
lightColor |
string |
'#FFD700' |
Color of lights |
boundaryThickness |
number |
12 |
Thickness of boundary ring |
boundaryOffset |
number |
8 |
Space between wheel and boundary |
| Prop | Type | Default | Description |
|---|---|---|---|
tapToSpin |
boolean |
true |
Enable tap-to-spin on center. Set to false when using external button. |
centerBackgroundColor |
string |
'#FFFFFF' |
Background color of center circle |
centerTextColor |
string |
'#000000' |
Text color of center ("SPIN"/"WAIT") |
| Prop | Type | Description |
|---|---|---|
onSpinStart |
() => void |
Called when spin starts |
onSpinning |
(angle: number) => void |
Called during spinning with current angle |
interface Reward {
id: string | number; // Unique identifier
label: string; // Display text on segment
value: any; // Reward value (coins, items, etc.)
color?: string; // Segment color (auto-generated if not provided)
textColor?: string; // Override default text color
icon?: ReactNode; // Optional icon/image (future feature)
probability?: number; // Weight for winning (default: 1). Ignored if winnerIndex is set.
}When using ref, the following methods are available:
interface WheelOfFortuneRef {
spin: () => void; // Trigger a spin programmatically
}const [canSpin, setCanSpin] = useState(true);
<WheelOfFortune
rewards={rewards}
onSpinEnd={(reward) => {
handleReward(reward);
setCanSpin(false);
// Re-enable after cooldown
setTimeout(() => setCanSpin(true), 60000);
}}
disabled={!canSpin}
/>You can use the exported utilities and hooks to build custom wheel implementations:
import {
useWheelAnimation,
WheelSegment,
selectRewardByProbability
} from 'react-native-prize-wheel';
// Build your custom wheel component- Use
useMemofor rewards array if it's dynamically generated - Keep reward count reasonable (6-12 segments optimal)
- Use
useNativeDriver: true(enabled by default) - Avoid heavy computations in callbacks
- Ensure
react-native-svgis installed - Check that rewards array is not empty
- Verify all required props are provided
- Ensure
useNativeDriver: trueis enabled (default) - Reduce wheel size if on low-end devices
- Check for heavy operations in callbacks
Contributions are welcome! Please open an issue or submit a PR.
MIT
