If the input has 20 sensible options, it should be a Dropdown, not a Textbox.
Your model never failed on an input you never let the user type.
gr.Dropdown renders a compact select menu from a list of choices. Single-select by default; add multiselect=True and it turns into a token-style multi-pick with removable chips. Your function receives the selected value (str, int, or float) — or a list of them when multiselect is on. Choices can also be (label, value) tuples, so the UI can show "gpt-oss-120b (fast)" while your code receives "gpt-oss-120b-fast".
Free-text input is an attack surface for your demo's sanity. A Dropdown turns 'whatever the user typed' into one of N options you control, which means your function's if-branches can actually be exhaustive. It is the cheapest way to make a Gradio demo feel like a product instead of a REPL: fewer validation branches in your code, no typos reaching your model, and the browser's native select behavior — type-ahead, keyboard nav — comes free.
import gradio as gr
def classify(text, model):
votes = {"distilbert": 0.87, "bert-base": 0.91, "roberta": 0.89}
p = votes[model]
label = "POSITIVE" if p > 0.5 else "NEGATIVE"
return f"{label} ({p:.0%} confidence via {model})"
with gr.Blocks() as demo:
gr.Markdown("## Sentiment classifier")
text = gr.Textbox(label="Review", placeholder="Paste a product review…")
model = gr.Dropdown(
choices=["distilbert", "bert-base", "roberta"],
value="distilbert",
label="Model",
info="Which checkpoint runs the inference",
)
out = gr.Textbox(label="Prediction")
gr.Button("Classify").click(classify, [text, model], out)
if __name__ == "__main__":
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 heading, a single-line Textbox labeled "Review", a closed select menu labeled "Model" showing "distilbert" preselected (the info text "Which checkpoint runs the inference" sits under the label), a Textbox labeled "Prediction", and a dark "Classify" button. Opening the menu lists exactly three options: distilbert, bert-base, roberta.
Every value the classifier sees comes from the choices list — no input validation needed in classify().
import gradio as gr
import pandas as pd
df = pd.read_csv("cities.csv")
def top5(country, metric):
sub = df[df.country == country]
if metric == "By population":
sub = sub.sort_values("population", ascending=False)
else:
sub = sub.sort_values("name")
return sub.head(5)
with gr.Blocks() as demo:
gr.Markdown("## City explorer")
with gr.Row():
country = gr.Dropdown(choices=sorted(df.country.unique().tolist()),
value="Norway", label="Country")
metric = gr.Radio(["By population", "Alphabetical"], value="By population",
label="Sort")
table = gr.Dataframe(label="Top 5")
country.change(top5, [country, metric], table)
metric.change(top5, [country, metric], table)
if __name__ == "__main__":
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 "City explorer" heading, one Row holding the "Country" dropdown (fed from df.country.unique() — here France, Germany, Norway) next to a two-option Radio, and a Dataframe below. Picking "Germany" immediately re-renders the table with Berlin, Hamburg, Munich — no button click, because country.change fired on selection.
Populate choices from real data at build time; .change() makes the explorer live-update on every pick.
import gradio as gr
def tag_image(tags, extra):
tags = tags + ([extra] if extra and extra not in tags else [])
return ", ".join(tags) if tags else "no tags"
with gr.Blocks() as demo:
gr.Markdown("## Image tagger")
tags = gr.Dropdown(
choices=["sunset", "portrait", "macro", "street", "aerial"],
multiselect=True, max_choices=3, label="Tags (max 3)",
)
extra = gr.Textbox(label="Custom tag", placeholder="Type your own…")
out = gr.Markdown()
gr.Button("Save").click(tag_image, [tags, extra], out)
if __name__ == "__main__":
demo.launch(prevent_thread_lock=True)
# After selecting "sunset" and "macro" and clicking Save:
print(tag_image(["sunset", "macro"], ""))* Running on local URL: http://127.0.0.1:7860 * To create a public link, set `share=True` in `launch()`. Rendered UI: a token-style multi-select labeled "Tags (max 3)" — clicking an option adds it as a removable chip, and the menu blocks a fourth pick. Below it the "Custom tag" Textbox and an empty Markdown pane. Clicking Save with sunset + macro selected renders "sunset, macro" in the Markdown output. Console: sunset, macro
multiselect=True means your function receives a list (empty list if nothing picked); max_choices caps selections in the UI.
| Flag | Meaning |
|---|---|
choices | List of str/int/float or (label, value) tuples; pass a function for lazy evaluation on page load. |
multiselect | Turns the menu into a chip-style multi-pick; your function then receives a list instead of a single value. |
max_choices | Caps how many options can be selected when multiselect=True; enforced in the UI, not just your code. |
allow_custom_value | Accepts typed text outside the choices list — that value reaches your function even though it matches nothing. |
type | "value" (default) passes the chosen value; "index" passes its integer position instead. |
filterable | Set False to disable type-to-filter for short lists where it's just noise. |
info | Muted helper text under the label — the right place for 'what are these options?' |
Dropdown is original Gradio — it appears in the library's earliest releases (pre-1.0) alongside Textbox, Radio, and Checkbox as one of the primitive input components. It has survived the 1.x → 2.x → 3.x → 4.x → 5.x/6.x rewrites with its core contract intact: choices in, value out.
Early Gradio evaluated everything eagerly at construction. Later versions let choices be a Callable, evaluated when the config renders — so a Dropdown can be fed from a database or API call that runs on page load, not at import time. And for the big-list case, Gradio 6.27.0 loads large Dropdown choices progressively on scroll (PR #13805) instead of shipping every option to the browser at once.
Dropdown.preprocess is where Gradio enforces the contract. When allow_custom_value is False and the browser sends a value not in choices, preprocess raises a Gradio Error before your function runs — the user sees a clean toast, your function never sees bad data. postprocess mirrors it in the other direction, warning if you set a value that isn't a declared choice. That's why a typo in your value= default produces a console warning rather than a crash.