gr.Blocks — freeform app layout with full control over data flow

Interface gives you an input→output pipeline. Blocks gives you the whole web app.

gr.Interface is one function in, one result out. The day that stops being enough, you open the Blocks box — and never really close it again.

What it does

gr.Blocks() is Gradio's layout-and-events canvas. You open it as a context manager, place components (Textbox, Slider, Chatbot, …) inside Row/Column/Tab containers in whatever arrangement you want, and wire them to Python functions with event listeners like .click() and .change(). Data flow is fully explicit: every listener declares its inputs and outputs by component reference, and one function's output can be the next function's input.

Why it matters

Interface hard-codes one shape: inputs on the left, outputs on the right, a Submit button in the middle. The moment your app needs tabs, a sidebar, a two-step pipeline (upload → preprocess → generate), or reactive updates that fire on typing, you have left Interface's territory. Blocks is also the substrate everything else runs on — gr.Interface literally subclasses gr.Blocks, gr.ChatInterface is a Blocks template, and gr.mount_gradio_app() expects a Blocks app. Learning it once pays off in every advanced Gradio feature.

Examples

import gradio as gr

with gr.Blocks(title="Sentiment Lab") as demo:
    gr.Markdown("## Sentiment Lab\nType a sentence, get a verdict.")
    with gr.Row():
        with gr.Column():
            inp = gr.Textbox(label="Sentence", placeholder="e.g. The service was excellent")
            btn = gr.Button("Analyze", variant="primary")
        with gr.Column():
            label = gr.Label(label="Verdict")
            conf = gr.Number(label="Confidence %")
    btn.click(lambda s: ("POSITIVE 0.94", 94.0), inputs=inp, outputs=[label, conf])

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

Rendered UI (verified on gradio 6.27.0):
- a title "Sentiment Lab" with a Markdown intro line
- one Row containing two Columns: left column stacks a Textbox labeled "Sentence" (with placeholder) above a primary-styled Button "Analyze"; right column stacks a Label "Verdict" above a Number "Confidence %"
- clicking Analyze fills Verdict with "POSITIVE 0.94" and Confidence % with 94 (function verified by direct call)

The classic Interface shape, rebuilt in Blocks. Everything Interface would have positioned for you, you now position yourself — that Row/Column nesting is the entire layout language.

import gradio as gr

with gr.Blocks() as demo:
    with gr.Tab("Convert"):
        t_in = gr.Textbox(label="Text")
        t_out = gr.Textbox(label="UPPERCASED")
        t_btn = gr.Button("Upper")
        t_btn.click(lambda s: s.upper(), inputs=t_in, outputs=t_out)
    with gr.Tab("Count"):
        c_in = gr.Textbox(label="Text")
        c_out = gr.Number(label="Characters")
        c_btn = gr.Button("Count")
        c_btn.click(lambda s: len(s), inputs=c_in, outputs=c_out)

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

Rendered UI (verified on gradio 6.27.0):
- two tab buttons "Convert" and "Count" above a panel
- Convert tab: Textbox "Text" → Button "Upper" → Textbox "UPPERCASED"
- Count tab: Textbox "Text" → Button "Count" → Number "Characters"
- each tab keeps its own independent component set and its own event listener

Grouping related demos as tabs is the single most common reason people first reach for Blocks — gr.Interface has no equivalent.

import gradio as gr

with gr.Blocks(title="Color Mixer") as demo:
    with gr.Row():
        r = gr.Slider(0, 255, value=180, label="Red")
        g = gr.Slider(0, 255, value=90, label="Green")
        b = gr.Slider(0, 255, value=58, label="Blue")
    swatch = gr.ColorPicker(label="Mixed color")
    for s in (r, g, b):
        s.change(lambda r, g, b: f"#{r:02x}{g:02x}{b:02x}",
                 inputs=[r, g, b], outputs=swatch)

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

Rendered UI (verified on gradio 6.27.0):
- one Row of three Sliders: "Red" (start 180), "Green" (90), "Blue" (58), each 0–255
- below, a ColorPicker labeled "Mixed color"
- moving any slider instantly updates the swatch: with defaults 180/90/58 it shows #b45a3a (mix function verified by direct call)

Three listeners, one shared output. Loops like this are the payoff of wiring components by reference — the same reactive pattern drives dashboards and live previews.

import gradio as gr

with gr.Blocks(title="Model Garden", analytics_enabled=False) as demo:
    gr.Markdown("## Model Garden")
    with gr.Accordion("Advanced options", open=False):
        temp = gr.Slider(0.0, 1.0, value=0.7, label="Temperature")
    chat = gr.Chatbot(label="Assistant")
    msg = gr.Textbox(label="Message")
    msg.submit(lambda m, h: (h + [{"role": "user", "content": m},
                                  {"role": "assistant", "content": f"echo: {m}"}],
                              ""),
               inputs=[msg, chat], outputs=[chat, msg])

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

Rendered UI (verified on gradio 6.27.0):
- Markdown heading "Model Garden"
- a collapsed Accordion "Advanced options" hiding a Temperature slider (0.0–1.0, default 0.7)
- a Chatbot labeled "Assistant" with an empty conversation, above a Textbox labeled "Message"
- pressing Enter in Message appends the user turn and an 'echo: …' assistant turn to the chat and clears the input (listener verified: appending [{role: user, content: m}, {role: assistant, content: 'echo: '+m}] to history, returning '' to clear)

A minimal chatbot as plain Blocks — this is roughly the skeleton ChatInterface wraps. Building it once yourself demystifies gr.ChatInterface forever.

Flags

FlagMeaning
with gr.Blocks() as demo:The context manager form is the idiom: components created inside the with-block register into this Blocks instance automatically.
demo.launch()Blocks apps launch exactly like Interface. Since Gradio 6, app-wide theme= and css= live on launch(), not on the constructor.
with gr.Row(): / with gr.Column():Layout containers are components too — nest them freely; each child lands inside the currently open container.
comp.click(fn, inputs, outputs)Event listeners hang off components, not the Blocks object; fn's arguments and return values map positionally onto inputs/outputs.
fill_height=TrueConstructor flag that stretches the app to the full viewport height — what you want for chat-style interfaces.
delete_cache=NoneConstructor option controlling how often Gradio clears cached example files; set to a tuple like (days, hours) for auto-cleanup.
demo.queue()Optional pre-launch step to enable the processing queue with default_concurrency_limit — required for streaming outputs.

Born in Gradio 3.0 (April 2022) — the biggest update ever

The 3.0 release notes call Blocks the headline feature of 'the biggest update to the library, ever': full control over data flows, layout, multiple tabs, and dynamic property updates. The team even ran a 'Gradio Blocks Party' on HuggingFace Spaces to launch it.

The context-manager API is the design

Gradio 3 shipped Blocks with with gr.Blocks() as demo: from day one — registration happens by Python context, not by passing lists. Gradio 6 (2026) later moved constructor cosmetics like theme/css to launch(), but the with-block core has barely changed since 2022.

What the with-block actually does

Blocks keeps a stack of currently-open Blocks contexts. gr.Textbox() asks the top of the stack to register itself and receive an id; that's why a component created outside any with-block can't wire events. When the context exits, Gradio freezes the layout into a config dict (mode, components, dependencies) that the Svelte frontend consumes as /config, and every .click()/.change() became one dependency entry mapping event → function → component ids. You can inspect it yourself with demo.get_config_file() — my tab demo serializes as {"mode": "blocks", "components": 5, "dependencies": 2}.

Fun facts

Pros

Cons

Takeaways