gradio · difficulty ◆◆
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.
AI-generated draft · reviewed and editedThis text was generated with AI as a first draft, then reviewed and edited by the author, who holds editorial responsibility for it.$ gr.Blocks()- 3.0 (May 2022)
- Version introduced
- 7
- Constructor parameters
- True
- Interface is a Blocks
Blocks was the flagship feature of the Gradio 3.0 release; gradio 3.0 hit PyPI on 2022-05-16
analytics_enabled, mode, title, fill_height, fill_width, delete_cache, kwargs — measured on gradio 6.27.0; layout/theme work happens in child containers and launch()
issubclass(gr.Interface, gr.Blocks) — verified on 6.27.0; ChatInterface composes Blocks too
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.
Example
$ 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 listenerGrouping 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.
Common flags
- 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=True
- Constructor flag that stretches the app to the full viewport height — what you want for chat-style interfaces.
- delete_cache=None
- Constructor 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.
- title
- Names the app — gr.Blocks(title="Echo Studio") lands in /config as title and in the page metadata; without it the app is called "Gradio".
History
Born in Gradio 3.0 (May 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.
Under the hood
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": 11, "dependencies": 2}, because the Tabs container, both TabItems and both Forms are components too, not just the six widgets.
Fun facts
Pros & cons
pros
- + Full layout control: rows, columns, tabs, accordions, side-by-side anything
- + Any-to-any event wiring: chained steps, shared outputs, reactive updates
- + It's the real API — Interface, ChatInterface, and mount_gradio_app all sit on top of it
cons
- − You write boilerplate Interface gave you for free (submit buttons, labels, flagging)
- − Bigger surface area: containers, events, and visibility each have their own quirks
- − No auto input/output columns — a careless Row order scrambles the UI
Takeaways
- 1Start every non-trivial demo with with gr.Blocks() as demo: — anything beyond one-in-one-out needs it.
- 2Components and event listeners must live in the same with-block; listeners can't wire outside an open Blocks context.
- 3Layout is nesting: Row and Column are containers you stack, Tab and Accordion are containers with chrome.
- 4Keep inputs/outputs lists and your function's return order in lockstep — positional mapping hides mistakes.
- 5Since Gradio 6, pass theme= and css= to launch(), not to gr.Blocks().
