# How to Build a Roulette Wheel Feature in Bubble

- Tool: Bubble
- Difficulty: Beginner
- Time required: 25-30 min
- Compatibility: All Bubble plans
- Last updated: March 2026

## TL;DR

A roulette wheel in Bubble is built using an HTML element with CSS animation for the spinning wheel and JavaScript for random outcome selection. You define prizes in a Data Type or Option Set, trigger the spin from a Bubble workflow, determine the winner server-side in a backend workflow to prevent cheating, and limit spins per user. This tutorial covers the full implementation from visual wheel to prize assignment.

## Overview: Building a Roulette Wheel in Bubble

This tutorial shows you how to build a spin-to-win wheel for promotions, gamification, or engagement features. You will create the visual wheel, implement fair random selection, assign prizes, and control spin frequency.

## Before you start

- A Bubble app with user authentication
- Basic understanding of HTML elements in Bubble
- Familiarity with custom states and backend workflows

## Step-by-step guide

### 1. Define prizes in an Option Set

Create an Option Set called 'WheelPrize' with options for each wheel segment: '10% Off', '20% Off', 'Free Shipping', 'Try Again', '$5 Credit', 'Try Again'. Add attributes: 'color' (text — hex code for the segment), 'weight' (number — probability weight, higher = more likely), 'value' (text — the actual prize code or description). Set 'Try Again' segments with higher weight for business-friendly odds.

**Expected result:** Prizes are defined with colors, probability weights, and values for each wheel segment.

### 2. Build the spinning wheel with HTML and CSS

Add a large HTML element (400x400px) to your page. Inside, create an SVG or CSS-based wheel divided into segments matching your prizes. Each segment is a colored slice with the prize label. Use CSS transform: rotate() for the spinning animation with an ease-out timing function for a realistic deceleration effect. Add a fixed pointer/arrow element outside the HTML that indicates the winning segment.

```
<style>
.wheel-container { position: relative; width: 350px; height: 350px; }
.wheel { width: 100%; height: 100%; border-radius: 50%; transition: transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99); }
.wheel.spinning { transform: rotate(var(--spin-degrees)); }
.pointer { position: absolute; top: -20px; left: 50%; transform: translateX(-50%); font-size: 30px; }
</style>
<div class="wheel-container">
  <div class="pointer">▼</div>
  <canvas id="wheel" width="350" height="350"></canvas>
</div>
```

**Expected result:** A colorful segmented wheel renders on the page with a pointer arrow indicating the winning position.

### 3. Determine the outcome server-side

Create a backend workflow 'determine_spin_result' with parameter 'user_id'. In the workflow, generate a random number weighted by the prize probabilities. Use 'Calculate random number' and map it to a prize based on cumulative weights. Return the winning prize option and the rotation angle needed to land on that segment. This prevents client-side cheating since the outcome is decided on the server.

> Pro tip: Always determine outcomes server-side. If you calculate the winner in the browser, users can inspect the JavaScript and know the result before spinning.

**Expected result:** A backend workflow determines the fair random outcome and returns the winning prize and rotation angle.

### 4. Trigger the spin and animate the result

Add a 'Spin' button. The workflow: Step 1 — check if the user has spins remaining (see next step). Step 2 — call the backend workflow to get the result. Step 3 — set a custom state 'spin_degrees' to the returned rotation angle. Step 4 — use a JavaScript action (via Toolbox plugin or HTML) to apply the CSS rotation. Step 5 — after a 4-second delay (matching the CSS transition), show a popup with the prize result. Step 6 — create a SpinRecord with the user, prize, and date.

**Expected result:** Clicking Spin triggers a smooth wheel animation that lands on the server-determined prize segment.

### 5. Limit spins per user

Add a 'spins_remaining' field (number) to the User Data Type, or track spins with a SpinRecord Data Type. Before allowing a spin, check: Search SpinRecords where user = Current User and date is today → count. If count is greater than or equal to your daily limit (e.g., 1), disable the Spin button and show 'Come back tomorrow!' message. Decrement spins_remaining or create a SpinRecord after each spin.

**Expected result:** Users are limited to a set number of spins per day, with the button disabled when the limit is reached.

### 6. Display and redeem the prize

After the spin animation completes, show a congratulatory popup with the prize name and a unique redemption code (generate using 'Calculate random string'). Store the code in a 'UserPrize' Data Type with user, prize, code, redeemed (yes/no), and expiry date. On the checkout or redemption page, validate the code against UserPrize records. Show a 'My Prizes' section in the user's profile listing unredeemed prizes.

**Expected result:** Winners receive unique redemption codes stored in the database, viewable in their profile and redeemable at checkout.

## Complete code example

File: `Workflow summary`

```text
ROULETTE WHEEL — WORKFLOW SUMMARY
====================================

OPTION SET: WheelPrize
  '10% Off' (weight: 20, color: #3B82F6)
  '20% Off' (weight: 10, color: #8B5CF6)
  'Free Shipping' (weight: 15, color: #10B981)
  'Try Again' (weight: 40, color: #6B7280)
  '$5 Credit' (weight: 15, color: #F59E0B)

DATA TYPES:
  SpinRecord: user, prize, date, rotation
  UserPrize: user, prize, code, redeemed, expiry

SPIN WORKFLOW:
  Step 1: Check daily spin limit
  Step 2: Backend → determine_spin_result
    Weighted random selection from prizes
    Returns: prize option + rotation degrees
  Step 3: Animate wheel to rotation degrees
  Step 4: Wait 4 seconds (animation duration)
  Step 5: Show prize popup
  Step 6: Create SpinRecord + UserPrize
  Step 7: Generate redemption code

SPIN LIMIT:
  Search SpinRecords (user + today) count
  If count >= 1: disable button, show message

REDEMPTION:
  Checkout: validate code against UserPrize
  Mark redeemed = yes after use
```

## Common mistakes

- **Calculating the winning outcome in client-side JavaScript** — Users can inspect browser code, see the result before spinning, or modify the outcome Fix: Always determine the outcome in a server-side backend workflow and send only the rotation angle to the client
- **Making all prizes equally likely regardless of value** — High-value prizes being as common as low-value ones can be costly and unsustainable for the business Fix: Use probability weights — assign higher weights to low-value prizes ('Try Again') and lower weights to high-value ones
- **Not tracking spins per user, allowing unlimited plays** — Users spam the wheel to farm prizes, undermining the promotion's value and potentially costing the business Fix: Implement per-user daily spin limits tracked via SpinRecord data type

## Best practices

- Determine outcomes server-side to prevent cheating
- Use weighted probability to control prize distribution
- Limit spins per user per day to maintain engagement over time
- Generate unique redemption codes for each prize won
- Set expiry dates on prizes to encourage prompt redemption
- Track all spins for analytics on engagement and prize distribution
- Make 'Try Again' the most common outcome (40-50% probability) for sustainability

## Frequently asked questions

### Is the wheel outcome truly random?

Yes, when using Bubble's random number generator in a backend workflow with probability weights. The outcome is determined server-side before the animation starts — the animation is purely visual.

### Can I change the prizes without rebuilding the wheel?

Yes. Since prizes are in an Option Set, you can change labels, colors, and weights from the Data tab. The wheel segments should be dynamically generated from the Option Set for full flexibility.

### How do I handle mobile users?

The CSS-animated wheel works on mobile browsers. Size the wheel to fit mobile screens (280-320px) and ensure the Spin button is large enough for touch interaction.

### Can I add sound effects to the spin?

Yes. Use an HTML5 audio element with a spinning sound that plays when the Spin button is clicked and stops after the animation completes.

### Can RapidDev build a custom promotional game for my Bubble app?

Yes. RapidDev can build custom promotional games including spin wheels, scratch cards, slot machines, and other engagement mechanics in Bubble.

---

Source: https://www.rapidevelopers.com/bubble-tutorial/implement-a-roulette-wheel-feature-in-bubble
© RapidDev — https://www.rapidevelopers.com/bubble-tutorial/implement-a-roulette-wheel-feature-in-bubble
