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.
Prerequisites
- A Bubble app with user authentication
- Basic understanding of HTML elements in Bubble
- Familiarity with custom states and backend workflows
Step-by-step guide
Define prizes in an Option Set
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.
Build the spinning wheel with HTML and CSS
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.
1<style>2.wheel-container { position: relative; width: 350px; height: 350px; }3.wheel { width: 100%; height: 100%; border-radius: 50%; transition: transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99); }4.wheel.spinning { transform: rotate(var(--spin-degrees)); }5.pointer { position: absolute; top: -20px; left: 50%; transform: translateX(-50%); font-size: 30px; }6</style>7<div class="wheel-container">8 <div class="pointer">▼</div>9 <canvas id="wheel" width="350" height="350"></canvas>10</div>Expected result: A colorful segmented wheel renders on the page with a pointer arrow indicating the winning position.
Determine the outcome server-side
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.
Trigger the spin and animate the result
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.
Limit spins per user
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.
Display and redeem the prize
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 working example
1ROULETTE WHEEL — WORKFLOW SUMMARY2====================================34OPTION SET: WheelPrize5 '10% Off' (weight: 20, color: #3B82F6)6 '20% Off' (weight: 10, color: #8B5CF6)7 'Free Shipping' (weight: 15, color: #10B981)8 'Try Again' (weight: 40, color: #6B7280)9 '$5 Credit' (weight: 15, color: #F59E0B)1011DATA TYPES:12 SpinRecord: user, prize, date, rotation13 UserPrize: user, prize, code, redeemed, expiry1415SPIN WORKFLOW:16 Step 1: Check daily spin limit17 Step 2: Backend → determine_spin_result18 Weighted random selection from prizes19 Returns: prize option + rotation degrees20 Step 3: Animate wheel to rotation degrees21 Step 4: Wait 4 seconds (animation duration)22 Step 5: Show prize popup23 Step 6: Create SpinRecord + UserPrize24 Step 7: Generate redemption code2526SPIN LIMIT:27 Search SpinRecords (user + today) count28 If count >= 1: disable button, show message2930REDEMPTION:31 Checkout: validate code against UserPrize32 Mark redeemed = yes after useCommon mistakes when building a Roulette Wheel Feature in Bubble
Why it's a problem: Calculating the winning outcome in client-side JavaScript
How to avoid: Always determine the outcome in a server-side backend workflow and send only the rotation angle to the client
Why it's a problem: Making all prizes equally likely regardless of value
How to avoid: Use probability weights — assign higher weights to low-value prizes ('Try Again') and lower weights to high-value ones
Why it's a problem: Not tracking spins per user, allowing unlimited plays
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a spin-to-win promotional wheel in Bubble.io with 6 prize segments, weighted probabilities, animated spinning, and one spin per user per day. How do I build this fairly and prevent cheating?
Build a roulette wheel feature. Create a WheelPrize option set with 6 prizes and probability weights. Add an animated wheel in an HTML element. Determine outcomes server-side. Limit users to one spin per day. Generate redemption codes for prizes.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation