kmail.at
← learning

gradio · difficulty ◆◆

gr.Label — the classifier output that draws its own confidence bars

Return {class: score}; gr.Label sorts it, cuts it to the top N and paints the bars — the rest of the looks are not yours to control.

gr.Label quietly deletes every class after the third, and it will happily draw a 250%-wide bar if you hand it raw logits.

2026-10-12 · 4 min read

$ gr.Label()

What it does

gr.Label takes one dict of {class_name: score} and renders a headline (the top class) plus one row per class: a native <meter> bar, the class name, and a whole-number percentage. Sorting happens server-side in postprocess(), which also slices to num_top_classes if you set it and returns LabelData(label=..., confidences=[...]) to the browser. Pass a str, int or float instead and the bars disappear — you just get the value as large text.

Why it matters

Classification output is the one piece of a demo nobody wants to hand-roll: sort the scores, name them, size the bars, keep it readable at 3 classes and at 1,000. gr.Label does all of it from two lines, and it puts aria-valuenow on every bar so a screen reader announces the confidence. Pair it with gr.Image or gr.Textbox and the classic classifier demo is done before the model finishes loading.

Example

$ import gradio as gr

def predict_intent(message):
    # stand-in for a real classifier — swap in model.predict_proba(...)[0]
    return {"shipping": 0.7212, "billing": 0.1506, "returns": 0.0789, "cancel": 0.0493}

with gr.Blocks(title="Support ticket router") as demo:
    gr.Markdown("### Support ticket router")
    msg = gr.Textbox(value="Where is order #10482?", label="customer message", lines=2)
    run = gr.Button("Route ticket", variant="primary")
    top = gr.Label(num_top_classes=3, label="predicted intent")
    run.click(predict_intent, inputs=msg, outputs=top)

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

Rendered UI (headless Chromium against this exact app, gradio 6.18.0 / Python 3.12):
- Markdown heading, then Textbox "customer message" (2 lines) and primary Button "Route ticket"
- below: Label "predicted intent" -> <h2 data-testid="label-output-value">shipping</h2>
- three <button class="confidence-set"> rows, one <meter> each:
    shipping   72%   value 0.7212, style="width: 72.12%"
    billing    15%   value 0.1506, style="width: 15.06%"
    returns     8%   value 0.0789, style="width: 7.89%"
- "cancel" (0.0493) is nowhere in the DOM — num_top_classes=3 sliced it off server-side

Verified by launching this exact snippet and clicking "Route ticket" in Chromium. The click POSTs {"data":["Where is order #10482?"],"fn_index":1,...}; the returned dict goes through the component's postprocess(), which sorts it and promotes the top key to the headline. The scores are a hard-coded stand-in — drop your own predict_proba() in the same slot.

$ import gradio as gr

def predict(message):
    return {"shipping": 0.7212, "billing": 0.1506, "returns": 0.0789, "cancel": 0.0493}

def on_select(evt: gr.SelectData):
    print(f"[select] index={evt.index} value={evt.value}")
    return f"queued: {evt.value}"

with gr.Blocks(title="Click a bar") as demo:
    msg = gr.Textbox(value="Where is order #10482?", label="customer message")
    run = gr.Button("Route ticket")
    top = gr.Label(num_top_classes=3, label="predicted intent")
    picked = gr.Textbox(label="agent queue")
    run.click(predict, inputs=msg, outputs=top)
    top.select(on_select, inputs=None, outputs=picked)

demo.launch()
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.
[select] index=1 value=billing

Rendered UI and server output, verified on 6.18.0:
- after "Route ticket", every confidence row carries the class "confidence-set group ... selectable" — the bars are real <button> elements
- clicking the second bar sends {"data":[],"event_data":{"index":1,"value":"billing"},"fn_index":2,...}
- the handler prints [select] index=1 value=billing and Textbox "agent queue" reads "queued: billing"

End-to-end verified: I clicked the second bar in Chromium and read the handler line off the server stdout. The payload has exactly two keys — index (0-based, matching display order) and value (the class string). Add the .select listener and /config flips the component's "_selectable" prop to true; without it the bars are inert.

$ import gradio as gr

def estimate_price(sqft, beds):
    return 118.4 * sqft + 9000 * beds      # a float, not a dict

with gr.Blocks(title="Price regressor") as demo:
    sqft = gr.Number(value=1200, label="square feet")
    beds = gr.Slider(1, 5, value=3, step=1, label="bedrooms")
    run = gr.Button("Estimate price")
    price = gr.Label(label="estimated price (USD)", color="#009245")
    run.click(estimate_price, inputs=[sqft, beds], outputs=price)

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

Rendered UI (verified on 6.18.0):
- Number "square feet" (1200), Slider "bedrooms" (1-5, step 1, value 3), Button "Estimate price"
- Label "estimated price (USD)" renders ONE element:
    <h2 data-testid="label-output-value" style="background-color: rgb(0, 146, 69);">169080.0</h2>
- <meter> element count: 0 — no scores, no bars
- postprocess() wrapped the float as LabelData(label="169080.0"): str(value), so the trailing .0 survives

Same component, three shapes: dict -> headline plus bars, float/str -> headline only, {} or None -> empty block. 118.4*1200 + 9000*3 = 169080.0 exactly, and that string is what the page shows — no thousands separator, no format hook. Read straight from the heading's textContent and style attribute.

Common flags

num_top_classes=3
How many classes reach the browser. The slice happens after sorting, so the dropped scores never leave Python — no 'and 3 more' affordance.
value={...}
A dict renders bars; a str/int/float renders only the headline (LabelData with confidences=null); {} or None renders an empty block.
color="#009245"
Fills the background of the headline <h2> and nothing else — verified as style="background-color: rgb(0, 146, 69);". Invisible once show_heading=False and bars are present.
show_heading=False
Hides the big headline so only the bars show — but ignored when there are no confidences, because a lone number still needs its label.
label="predicted intent"
Block caption above the component; inside gr.Interface it becomes the output parameter name shown beside the result.
.select
Attach a listener and each row becomes a clickable <button class="confidence-set selectable">; with no listener the component reports _selectable: false in /config.
scale / min_width
Layout only: how the block shares space in a Row and when it wraps. Defaults min_width=160, so a Label next to two inputs happily wraps on mobile.

History

A deprecated shim that outlived its module

By gradio 3.50.2 the public name was a three-parameter re-export: class Label(components.Label) lived in gradio/outputs.py and its __init__ called warn_outputs_deprecation(). The real implementation already sat in gradio/components/label.py (7,849 bytes at that tag) — and that is still where it lives. 4.0.0 kept the module and gave the component the full Component surface: color, elem_id, scale, render, later key and inputs.

show_heading took until 5.7.0

Hiding the headline became possible in 5.7.0 via PR #9987 by @hannahblair, per the repo CHANGELOG. I diffed the file at both ends of the range: show_heading is absent from gradio/components/label.py at 5.0.0 and present at 5.20.0. preserved_by_key — the 'keep this value across page reloads' switch — appears between those same two tags (absent at 5.20.0, present at 5.30.0).

Fun facts

Pros & cons

pros

  • + Two lines from model scores to a finished result block: sorting, the top-N cut, bars, percentages and screen-reader semantics all come free.
  • + Accepts three value shapes — dict for classification, str for a single class, float/int for regression — so the output type can change without swapping the component.
  • + Attach .select and each bar turns into a clickable drill-down, which converts a classifier into a filter UI with zero custom JavaScript.

cons

  • − The top-N cut is silent: a four-class model with num_top_classes=3 shows three rows and never hints that a fourth class existed.
  • − No clamping: logits produce >100% bars that overflow the block, so you must normalise yourself.
  • − The display rounds with Math.round(conf*100), so 0.9965 and 1.0 look identical — awkward if your demo is about uncertainty.

Takeaways

  1. 1Return {class_name: score}; the highest-scoring key becomes the headline and the bars sort themselves.
  2. 2Normalise before you return — softmax or divide by the sum — because there is no clamping and '250%' is a real render.
  3. 3Set num_top_classes explicitly; leave it None only if you want every class the model emitted drawn as a row.
  4. 4Attach .select if the bars should be clickable: the payload is exactly {'index': i, 'value': 'label'}.
  5. 5If a probability must not read as certainty, format it yourself first — the browser rounds to whole percent.

Related commands