spin-wheel / README.md
ysharma's picture
ysharma HF Staff
Update README.md
099f476 verified

A newer version of the Gradio SDK is available: 6.13.0

Upgrade
metadata
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

  1. Requirements:

    • Python 3.10+
    • Gradio 6.0+
  2. Install Gradio:

    pip install gradio>=6.0
    
  3. Add the component:

    Copy spin_wheel.py to 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() or update_wheel() to update the component. Never return a new SpinWheel() 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:

  1. 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"]}'
    
  2. 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

  1. Start your Gradio app with MCP:

    demo.launch(mcp_server=True)
    
  2. Configure Claude Desktop (claude_desktop_config.json):

    {
      "mcpServers": {
        "spinwheel": {
          "command": "npx",
          "args": [
            "mcp-remote",
            "http://localhost:7860/gradio_api/mcp/sse"
          ]
        }
      }
    }
    
  3. 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