Spaces:
Runtime error
A newer version of the Gradio SDK is available: 6.13.0
title: Spin Wheel
emoji: ๐ก๐ซ
colorFrom: green
colorTo: gray
sdk: gradio
sdk_version: 6.5.1
app_file: spin_wheel.py
pinned: false
license: mit
short_description: A Spin-to-Win Prize Wheel with gradio HTML custom component
๐ฐ SpinWheel Component Documentation
A customizable, animated prize wheel component for Gradio 6 applications. Perfect for giveaways, random selection, decision making, and gamification.
Table of Contents
- Installation
- Quick Start
- Component API
- Customization
- Event Handling
- Updating the Wheel
- REST API Usage
- MCP (Model Context Protocol) Usage
- Examples
- Troubleshooting
Installation
Requirements:
- Python 3.10+
- Gradio 6.0+
Install Gradio:
pip install gradio>=6.0Add the component:
Copy
spin_wheel.pyto your project, then import:from spin_wheel import SpinWheel, update_wheel, PRESETS
Quick Start
Minimal Example
import gradio as gr
from spin_wheel import SpinWheel
with gr.Blocks() as demo:
wheel = SpinWheel()
result = gr.Textbox(label="Winner")
wheel.change(lambda x: x, inputs=wheel, outputs=result)
demo.launch()
With Custom Segments
import gradio as gr
from spin_wheel import SpinWheel
my_segments = [
{"label": "๐ Pizza", "color": "#FF6B6B"},
{"label": "๐ Burger", "color": "#4ECDC4"},
{"label": "๐ฎ Tacos", "color": "#45B7D1"},
{"label": "๐ฃ Sushi", "color": "#FFEAA7"},
]
with gr.Blocks() as demo:
wheel = SpinWheel(segments=my_segments)
result = gr.Textbox(label="Tonight's Dinner")
wheel.change(lambda x: f"You're having {x}!", inputs=wheel, outputs=result)
demo.launch()
Component API
SpinWheel Constructor
SpinWheel(
value=None, # str | None - Current/last winning label
segments=None, # list[dict] | None - Wheel segments (see below)
segments_json=None, # str | None - Pre-computed segments JSON (internal use)
rotation=0, # float - Current wheel rotation in degrees
**kwargs # Additional gr.HTML arguments
)
Segment Format
Each segment is a dictionary with:
| Key | Type | Required | Description |
|---|---|---|---|
label |
str |
โ | Display text (supports emoji) |
color |
str |
โ | Hex color code (e.g., "#FF6B6B") |
weight |
int |
โ | Relative size weight (default: 1) |
Example with weights:
segments = [
{"label": "๐ Grand Prize", "color": "#FFD700", "weight": 1}, # Small slice
{"label": "โญ 100 XP", "color": "#4ECDC4", "weight": 3}, # 3x larger
{"label": "๐ Try Again", "color": "#96CEB4", "weight": 5}, # 5x larger
]
Built-in Presets
Import and use pre-defined segment configurations:
from spin_wheel import PRESETS
# Available presets:
# - "Default" - 8 prize segments
# - "Yes/No" - Binary decision
# - "Restaurant Picker" - 6 food options
# - "Team Selector" - 4 colored teams
# - "Priority Picker" - 5 priority levels
wheel = SpinWheel(segments=PRESETS["Restaurant Picker"])
Customization
Segment Colors
Use any valid CSS hex color:
segments = [
{"label": "Red", "color": "#FF0000"},
{"label": "Green", "color": "#00FF00"},
{"label": "Blue", "color": "#0000FF"},
{"label": "Custom", "color": "#8B5CF6"}, # Purple
]
Recommended Color Palettes
Vibrant:
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7", "#DDA0DD"]
Pastel:
colors = ["#FFB5BA", "#B5DEFF", "#B5FFB5", "#FFE5B5", "#E5B5FF", "#B5FFF5"]
Dark Mode:
colors = ["#6366F1", "#8B5CF6", "#A855F7", "#D946EF", "#EC4899", "#F43F5E"]
Event Handling
Available Events
| Event | Triggered When |
|---|---|
.change() |
Wheel stops and winner is determined |
.input() |
Same as change (user interaction) |
.click() |
Component is clicked |
Basic Event Handler
def on_spin_complete(winner):
return f"Congratulations! You won: {winner}"
wheel.change(fn=on_spin_complete, inputs=wheel, outputs=result_textbox)
Tracking Spin History
with gr.Blocks() as demo:
wheel = SpinWheel()
history = gr.State([])
history_display = gr.Textbox(label="History", lines=5)
def track_wins(winner, hist):
hist = hist or []
hist.append(winner)
return "\n".join(reversed(hist[-10:])), hist
wheel.change(
fn=track_wins,
inputs=[wheel, history],
outputs=[history_display, history]
)
Updating the Wheel
โ ๏ธ Critical: Always use
gr.HTML()orupdate_wheel()to update the component. Never return a newSpinWheel()instance from event handlers.
Using the Helper Function
from spin_wheel import update_wheel, PRESETS
def change_to_preset(preset_name):
return update_wheel(segments=PRESETS[preset_name])
preset_dropdown.change(
fn=change_to_preset,
inputs=preset_dropdown,
outputs=wheel
)
Direct gr.HTML() Updates
import json
from spin_wheel import compute_segment_data
def add_segment(name, color, current_segments):
new_segments = current_segments + [{"label": name, "color": color}]
segment_data = compute_segment_data(new_segments)
return gr.HTML(segments_json=json.dumps(segment_data), rotation=0), new_segments
add_btn.click(
fn=add_segment,
inputs=[name_input, color_input, segments_state],
outputs=[wheel, segments_state]
)
Reset the Wheel
def reset():
return gr.HTML(segments_json=json.dumps(compute_segment_data(DEFAULT_SEGMENTS)),
value=None,
rotation=0)
reset_btn.click(fn=reset, outputs=wheel)
REST API Usage
When you launch a Gradio app, it automatically exposes a REST API.
Enable API
demo.launch() # API enabled by default at /api/
API Endpoints
Get Current Value
curl -X POST http://localhost:7860/api/predict \
-H "Content-Type: application/json" \
-d '{"data": []}'
Trigger Spin Programmatically
The wheel spin is triggered client-side (JavaScript), so you can't directly trigger a spin via API. However, you can:
Set the winner directly:
# In your Gradio app, add an API endpoint def set_winner(winner_label): return gr.HTML(value=winner_label) set_btn = gr.Button("Set Winner") set_btn.click(fn=set_winner, inputs=winner_input, outputs=wheel, api_name="set_winner")curl -X POST http://localhost:7860/api/set_winner \ -H "Content-Type: application/json" \ -d '{"data": ["๐ Grand Prize"]}'Change segments via API:
def update_segments_api(segments_list): return update_wheel(segments=segments_list) # Hidden button for API access api_btn = gr.Button(visible=False) api_btn.click(fn=update_segments_api, inputs=segments_json_input, outputs=wheel, api_name="update_segments")
Python Client
from gradio_client import Client
client = Client("http://localhost:7860")
# Call your custom API endpoints
result = client.predict(
"๐ Grand Prize", # winner label
api_name="/set_winner"
)
print(result)
JavaScript Client
import { Client } from "@gradio/client";
const client = await Client.connect("http://localhost:7860");
const result = await client.predict("/set_winner", {
data: ["๐ Grand Prize"]
});
console.log(result);
MCP Usage
The SpinWheel component works with Gradio's MCP (Model Context Protocol) support, allowing AI assistants to interact with your wheel.
Enable MCP Server
demo.launch(mcp_server=True)
This starts an MCP server alongside your Gradio app.
MCP Tool Schema
The SpinWheel component exposes this schema via api_info():
{
"type": "string",
"description": "The label of the winning segment"
}
Connecting Claude Desktop to Your MCP Server
Start your Gradio app with MCP:
demo.launch(mcp_server=True)Configure Claude Desktop (
claude_desktop_config.json):{ "mcpServers": { "spinwheel": { "command": "npx", "args": [ "mcp-remote", "http://localhost:7860/gradio_api/mcp/sse" ] } } }Interact via Claude:
- "What are the available tools?"
- "Set the wheel winner to '๐ Grand Prize'"
- "Update the wheel segments to a yes/no configuration"
Custom MCP Tools
Add dedicated tools for the wheel:
import gradio as gr
from spin_wheel import SpinWheel, update_wheel, PRESETS
import random
with gr.Blocks() as demo:
wheel = SpinWheel()
result = gr.Textbox(label="Result")
segments_state = gr.State(PRESETS["Default"])
# MCP-friendly tool: Random spin (server-side)
def random_spin(segments):
"""Randomly select a winner from the wheel segments."""
winner = random.choice(segments)["label"]
return gr.HTML(value=winner), winner
spin_api_btn = gr.Button(visible=False)
spin_api_btn.click(
fn=random_spin,
inputs=segments_state,
outputs=[wheel, result],
api_name="spin_wheel" # Exposed as MCP tool
)
# MCP-friendly tool: Change preset
def change_preset_api(preset_name: str):
"""Change the wheel to a preset configuration.
Args:
preset_name: One of 'Default', 'Yes/No', 'Restaurant Picker', 'Team Selector', 'Priority Picker'
"""
if preset_name not in PRESETS:
return gr.HTML(), f"Unknown preset: {preset_name}"
return update_wheel(segments=PRESETS[preset_name]), f"Changed to {preset_name}"
preset_api_btn = gr.Button(visible=False)
preset_api_btn.click(
fn=change_preset_api,
inputs=gr.Textbox(visible=False),
outputs=[wheel, result],
api_name="change_preset" # Exposed as MCP tool
)
demo.launch(mcp_server=True)
MCP Tool Descriptions
For better AI interaction, add docstrings to your functions:
def spin_wheel(segments: list) -> str:
"""
Spin the prize wheel and return a random winner.
Use this tool when the user wants to:
- Make a random selection
- Pick a winner for a giveaway
- Make a decision randomly
Returns:
The label of the winning segment (e.g., "๐ Grand Prize")
"""
return random.choice(segments)["label"]
Examples
Giveaway Wheel
import gradio as gr
from spin_wheel import SpinWheel
participants = [
{"label": "Alice", "color": "#FF6B6B"},
{"label": "Bob", "color": "#4ECDC4"},
{"label": "Charlie", "color": "#45B7D1"},
{"label": "Diana", "color": "#96CEB4"},
{"label": "Eve", "color": "#FFEAA7"},
]
with gr.Blocks() as demo:
gr.Markdown("# ๐ Giveaway Winner Selector")
wheel = SpinWheel(segments=participants)
winner_box = gr.Textbox(label="๐ Winner", scale=2)
wheel.change(
lambda x: f"๐ Congratulations {x}! You won! ๐",
inputs=wheel,
outputs=winner_box
)
demo.launch()
Decision Maker with History
import gradio as gr
from spin_wheel import SpinWheel, update_wheel
import json
with gr.Blocks() as demo:
gr.Markdown("# ๐ค Decision Maker")
with gr.Row():
wheel = SpinWheel(segments=[
{"label": "Do it now!", "color": "#4ECDC4"},
{"label": "Sleep on it", "color": "#45B7D1"},
{"label": "Ask a friend", "color": "#96CEB4"},
{"label": "Flip a coin", "color": "#FFEAA7"},
])
with gr.Column():
decision = gr.Textbox(label="Decision")
history = gr.Dataframe(
headers=["#", "Decision", "Time"],
label="History"
)
decisions_state = gr.State([])
def record_decision(winner, hist):
import datetime
hist = hist or []
hist.append([len(hist)+1, winner, datetime.datetime.now().strftime("%H:%M:%S")])
return winner, hist[-10:], hist
wheel.change(
fn=record_decision,
inputs=[wheel, decisions_state],
outputs=[decision, history, decisions_state]
)
demo.launch()
Weighted Probability Wheel
import gradio as gr
from spin_wheel import SpinWheel
# Common items have higher weight (larger slice)
loot_table = [
{"label": "๐ Legendary", "color": "#FFD700", "weight": 1}, # 1/20 = 5%
{"label": "๐ Epic", "color": "#9B59B6", "weight": 4}, # 4/20 = 20%
{"label": "๐ Rare", "color": "#3498DB", "weight": 5}, # 5/20 = 25%
{"label": "๐ Common", "color": "#2ECC71", "weight": 10}, # 10/20 = 50%
]
with gr.Blocks() as demo:
gr.Markdown("# ๐ฎ Loot Box Simulator")
gr.Markdown("*Legendary: 5% โข Epic: 20% โข Rare: 25% โข Common: 50%*")
wheel = SpinWheel(segments=loot_table)
result = gr.Textbox(label="You Got")
wheel.change(lambda x: x, inputs=wheel, outputs=result)
demo.launch()
Troubleshooting
Wheel doesn't spin
Cause: JavaScript not loading properly.
Fix: Check browser console for errors. Ensure you're using Gradio 6.0+.
Wheel resets position after spin
Cause: Using old code without rotation prop.
Fix: Update to latest spin_wheel.py which persists rotation:
<div class="wheel" style="transform: rotate(${rotation || 0}deg);">
Duplicate entries in history
Cause: Calling trigger('change') after setting props.value.
Fix: Remove explicit triggerโsetting props.value auto-triggers the event:
// โ
Correct
props.value = winner.label;
// โ Wrong - causes duplicate
props.value = winner.label;
trigger('change');
Preset change doesn't update wheel
Cause: Returning SpinWheel() instance instead of gr.HTML().
Fix: Always use the helper function:
# โ
Correct
return update_wheel(segments=new_segments)
# โ Wrong
return SpinWheel(segments=new_segments)
Winner doesn't match where wheel stopped
Cause: Rotation tracking math error.
Fix: Use latest code that tracks full rotation and verifies winner:
let totalRotation = parseFloat(props.rotation) || 0;
// ... after spin ...
totalRotation = finalRotation; // Don't normalize!
props.rotation = totalRotation;
License
MIT License - Feel free to use in your projects!
Contributing
Issues and PRs welcome! Please include:
- Gradio version
- Browser and OS
- Minimal reproduction code
- Console errors (if any)
Built with โค๏ธ for the Gradio community