DEV Community

Mert Ercelik
Mert Ercelik

Posted on

Building a Customizable Prize Wheel in React with TypeScript

Prize wheels are popular interactive elements in web applications for gamification, contests, and promotional campaigns. This guide walks through building a customizable, production-ready prize wheel component using React and TypeScript.

Overview

After evaluating existing solutions, most prize wheel libraries lacked flexibility, TypeScript support, or modern React patterns. This implementation addresses these gaps with a fully typed, customizable component built on modern web technologies.

Technical Stack

  • React 18 for the component architecture
  • TypeScript 5 for type safety
  • GSAP for smooth animations
  • SVG for scalable rendering
  • Vite for build tooling

Core Features

The component provides several essential capabilities:

Probability-Based Selection

Not all prizes should have equal chances. The system uses weighted random selection where each sector has a probability value:

const sectors = [
  { id: 1, label: 'Common', probability: 50 },   // 50% chance
  { id: 2, label: 'Uncommon', probability: 30 }, // 30% chance
  { id: 3, label: 'Rare', probability: 15 },     // 15% chance
  { id: 4, label: 'Legendary', probability: 5 }, // 5% chance
];
Enter fullscreen mode Exit fullscreen mode

Extensive Customization

Every visual aspect is configurable: colors, shadows, borders, text styling, and animation timing.

Programmatic Control

The wheel exposes a ref API for triggering spins and tracking state:

const wheelRef = useRef<PrizeWheelRef>(null);

wheelRef.current?.spin();
const isSpinning = wheelRef.current?.isSpinning;
Enter fullscreen mode Exit fullscreen mode

Implementation

Here's a basic implementation:

import { useRef } from 'react';
import { PrizeWheel } from '@mertercelik/react-prize-wheel';
import type { Sector, PrizeWheelRef } from '@mertercelik/react-prize-wheel';
import '@mertercelik/react-prize-wheel/style.css';

function App() {
  const wheelRef = useRef<PrizeWheelRef>(null);

  const sectors: Sector[] = [
    { id: 1, label: 'Prize 1', probability: 10 },
    { id: 2, label: 'Prize 2', probability: 20 },
    { id: 3, label: 'Prize 3', probability: 15 },
    { id: 4, label: 'Prize 4', probability: 5 },
  ];

  const handleSpinEnd = (sector: Sector) => {
    console.log('Winner:', sector.label);
  };

  return (
    <div>
      <PrizeWheel
        ref={wheelRef}
        sectors={sectors}
        onSpinEnd={handleSpinEnd}
      />
      <button onClick={() => wheelRef.current?.spin()}>
        Spin the Wheel
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Customization Options

The component supports extensive styling:

<PrizeWheel
  ref={wheelRef}
  sectors={sectors}
  onSpinStart={handleSpinStart}
  onSpinEnd={handleSpinEnd}
  duration={6}
  minSpins={4}
  maxSpins={4}
  frameColor="#ffd700"
  middleColor="#ffd700"
  middleDotColor="#8b7500"
  winIndicatorColor="#ffd700"
  winIndicatorDotColor="#8b7500"
  sticksColor="#ffd700"
  wheelColors={['#0a1d3f', '#0a2249']}
  borderColor="#ffd700"
  borderWidth={3}
  textColor="#ffffff"
  textFontSize={20}
  wheelShadowColor="#000"
  wheelShadowOpacity={0.2}
  middleShadowColor="#000"
  middleShadowOpacity={0.25}
  indicatorShadowColor="#000"
  indicatorShadowOpacity={0.3}
/>
Enter fullscreen mode Exit fullscreen mode

Animation System

GSAP handles the rotation with easing for natural deceleration. The wheel calculates the final rotation angle based on the selected sector and applies random spins for visual effect.

SVG Rendering

Using SVG ensures the wheel scales perfectly on any screen size. The component generates sector paths dynamically based on the number of sectors and handles text positioning with automatic wrapping for long labels.

Probability Algorithm

The weighted random selection works by calculating total probability weight and using a random value to select a sector:

const totalWeight = sectors.reduce((sum, s) => sum + s.probability, 0);
let random = Math.random() * totalWeight;

for (const sector of sectors) {
  random -= sector.probability;
  if (random <= 0) return sector;
}
Enter fullscreen mode Exit fullscreen mode

Use Cases

This approach works well for:

  • E-commerce promotional campaigns
  • Gaming reward systems
  • Event raffles
  • Interactive landing pages
  • Loyalty programs

Installation

npm install @mertercelik/react-prize-wheel
Enter fullscreen mode Exit fullscreen mode

Live Demo

You can try it here: https://mertercelik.github.io/react-prize-wheel/

Resources

Conclusion

Building a reusable wheel component requires careful consideration of customization, animation performance, and probability distribution. This implementation provides a flexible solution that handles various use cases while maintaining type safety and smooth performance.

The project is open source under MIT license. The code and documentation are available on GitHub.

Credit: SVG wheel design inspired by Soma Szoboszlai's work.

Top comments (0)