gr.Dropdown — pick from a list instead of trusting free text

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.

What it does

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".

Why it matters

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.

Examples

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.

Flags

FlagMeaning
choicesList of str/int/float or (label, value) tuples; pass a function for lazy evaluation on page load.
multiselectTurns the menu into a chip-style multi-pick; your function then receives a list instead of a single value.
max_choicesCaps how many options can be selected when multiselect=True; enforced in the UI, not just your code.
allow_custom_valueAccepts 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.
filterableSet False to disable type-to-filter for short lists where it's just noise.
infoMuted helper text under the label — the right place for 'what are these options?'

There since the beginning

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.

Choices became lazy (and long lists got fast)

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.

What preprocess actually guards

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.

Fun facts

Pros

Cons

Takeaways