gradio Slider — bounded numeric input nobody can break

Minimum, maximum, step — three numbers and the user physically cannot enter an invalid value.

Textboxes trust users; Sliders don't. That's why every LLM demo you've ever used reaches for one.

What it does

gr.Slider(min, max) renders a draggable track between two bounds and hands your function a float (or int, with precision=0) when it fires. value sets the starting position, step snaps the drag to fixed increments. Out-of-range input can't happen from the UI — and if a value arrives out of bounds anyway (API call, manual postprocess), the backend raises gr.Error. It carries the standard toolkit: label, info, scale, visible, interactive, elem_id, plus three listeners — .change, .input and .release.

Why it matters

Every ML demo has tuning knobs: temperature, top-p, confidence thresholds, batch size, seed. A Textbox accepts "0,7" and "banana"; a Slider makes those failures structurally impossible — the user can only produce values you already declared legal. That's why the temperature slider is the single most recognizable control in all of HuggingFace Spaces. And unlike most components, Slider can randomize itself: randomize=True picks a fresh value from the range on every page load.

Examples

import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("## Temperature sweep")
    temp = gr.Slider(0.1, 2.0, value=0.7, step=0.1,
                     label="temperature", info="higher = wilder completions")
    out = gr.Textbox(label="verdict")
    temp.change(lambda t: "chaotic" if t > 1.2 else "sane",
                inputs=temp, outputs=out)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

Rendered UI: a markdown header, then a slider track from 0.1 to 2.0 with the handle at 0.7, the label "temperature" on top and smaller gray helper text "higher = wilder completions" under it; below, a Textbox labeled "verdict" showing "sane". Dragging the handle past 1.2 flips the verdict to "chaotic" — while still dragging, .input fires continuously; on let-go, .release fires once.

Run and verified with gradio 6.28.0: built and launched clean, printing exactly the startup lines shown. The classic LLM-demo knob — this exact pattern is all over HuggingFace Spaces.

import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("## Image classifier (mock)")
    conf = gr.Slider(0, 1, value=0.5, label="confidence threshold")
    gallery_lbl = gr.Label(num_top_classes=2, label="predictions")
    conf.release(lambda c: {"cat": 0.9, "dog": 0.6},
                 inputs=conf, outputs=gallery_lbl)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

Rendered UI: a slider from 0 to 1 at 0.5 labeled "confidence threshold" and a Label component showing "cat: 90%, dog: 60%". Drag the threshold and the predictions re-render on release — one event, one dict, no parsing.

Slider feeding a Label is the standard classifier demo shape. Verified launch on 6.28.0. Note the listener is .release — fires once when the drag ends, not on every pixel of movement.

import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("## Random hyperparameter picker")
    seed = gr.Slider(0, 10000, randomize=True,
                     label="seed", info="re-randomized on every page load")
    shown = gr.Number(label="seed in play")
    seed.change(lambda s: s, inputs=seed, outputs=shown)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

Rendered UI: a slider from 0 to 10000 whose handle sits at a random position (randomize=True overrides any value=), helper text under the label, and a Number box showing the seed. Reload the page and the handle lands somewhere new; a fresh gr.Slider(-100, 100, randomize=True) drew 7 distinct values across 8 instances when I checked.

randomize=True makes the server pick a uniform-random value in [min, max] per page load. Verified in 6.28.0. Cheapest A/B roulette there is — no numpy, no gr.State.

import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("## Queue rate limiter")
    concurrency = gr.Slider(1, 8, value=2, step=1, precision=0,
                            label="concurrency limit")
    status = gr.Textbox(label="status")
    concurrency.input(lambda n: f"queue at {n} workers",
                      inputs=concurrency, outputs=status)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

Rendered UI: a slider from 1 to 8 snapping to whole numbers with the handle at 2, and a status Textbox updating live to e.g. "queue at 3 workers" as the handle moves. precision=0 makes the value a Python int, not a float — n arrives as 3, never 3.0.

precision=0 is the int switch: the docstring says it rounds to the nearest integer and converts the type. Verified with preprocess in 6.28.0 — a Slider(0,10,step=1).preprocess(7) returned int 7. Pair .input for live feedback with .release for the expensive call.

Flags

FlagMeaning
minimum, maximum (first two args)The bounds — positional and first. Everything the user can produce lives inside them.
step=0.1Snap increment while dragging. step=1 with precision=0 gives you a clean integer dial.
precision=0Rounds to int and converts the value type to int; None (default) leaves floats untouched.
randomize=TrueIgnores value= and draws a uniform-random start from the range on each page load — verified live in 6.28.0.
info="..."Small markdown line under the label — the natural home for what the knob actually does.
.input vs .release.input fires continuously during the drag, .release fires once on let-go — pair them for live preview + expensive compute.
value=lambda: ...Pass a function and Gradio calls it on every app load to compute the start value; with inputs= it recalculates when those components change.

Core since the 2.x era

Slider has been part of Gradio's core input set since the early days — it's one of the components that survived the 2.x → 3.x → 4.x rewrites with its min/max/step contract intact, and it predates most of the modern layout system.

The .release event and the 6.x touches

The dedicated .release listener (fire once when the drag ends, instead of spamming .change) arrived in the 4.x era and is the reason sliders stopped feeling laggy in heavy demos. In the 6.x line the signature grew modern plumbing — key/preserved_by_key for gr.render re-binding and a buttons parameter — while preprocess stayed pass-the-float-through.

Under the hood: three events, one socket

When the user drags, the frontend Svelte component emits input events over the running WebSocket/SSE session; each fires your .input listeners server-side with the raw number. On mouse-up the frontend sends a single release event — which is why .release exists as a separate listener instead of you debouncing .change with timers. On the way into your function, preprocess checks the bounds and applies precision (out-of-range input raises gr.Error before your code runs); on the way out, postprocess coerces whatever you return — I verified postprocess("4") on an int slider returns int 4 — so even a string from an LLM-generated config lands as a sane number.

Fun facts

Pros

Cons

Takeaways