fn, inputs, outputs — that's the whole contract, and Gradio builds the app around it.
Every Hugging Face Space you have ever played with started life as three arguments: fn, inputs, outputs.
gr.Interface wraps a single Python function in a complete web UI: input components on the left, output components on the right, Submit, Clear and Flag buttons generated for you. You pass fn (the function), inputs (one component or a list, one per argument), and outputs (one per return value). Strings are accepted as shortcuts — "text" is gr.Textbox(), "image" is gr.Image(), "number" is gr.Number(). Gradio handles the HTTP server, the queue, and the JSON plumbing; you only write the function.
It is the lowest-energy path from "I have a function" to "people can use it in a browser", which is why it became the default format for model demos on Hugging Face Spaces. Each event listener is auto-named after your function (fn=classify becomes a public API endpoint /classify), so the same demo serves the browser UI, gradio_client calls, and — since 5.28 — MCP tools. One Interface, three doors into your model.
import gradio as gr
def reverse_text(text: str) -> str:
return text[::-1]
demo = gr.Interface(
fn=reverse_text,
inputs=gr.Textbox(label="Your text", placeholder="Type here..."),
outputs=gr.Textbox(label="Reversed"),
title="Text Reverser",
examples=[["Gradio makes ML demos easy"]],
)
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7861
* To create a public link, set `share=True` in `launch()`.
Rendered UI (verified via /config):
- centered Markdown title "Text Reverser"
- left column: Textbox labeled "Your text" (placeholder "Type here...")
- below it: Clear and Submit buttons
- right column: Textbox labeled "Reversed", below it a Flag button
- bottom: an examples table "Your text | Gradio makes ML demos easy"
Event listener: targets=[Submit.click, Textbox.submit] inputs=[textbox] outputs=[textbox], api_name="/reverse_text"
End-to-end: gradio_client predict("Hello Gradio") -> 'oidarG olleH'
Verified on gradio 6.27.0 (Python 3.12). The Submit button binds to .click AND Enter (.submit) on the input; the API endpoint is named after the function, not 'predict'.
import gradio as gr
from PIL import Image
import numpy as np
def classify(img: Image.Image):
if img is None:
return {}
arr = np.asarray(img.convert("L"), dtype=float)
brightness = arr.mean() / 255.0
if brightness > 0.6:
return {"sky": 0.83, "cloud": 0.12, "night": 0.05}
elif brightness > 0.3:
return {"forest": 0.61, "meadow": 0.27, "sky": 0.12}
else:
return {"night": 0.77, "cave": 0.18, "forest": 0.05}
demo = gr.Interface(
fn=classify,
inputs=gr.Image(type="pil", label="Input image"),
outputs=gr.Label(num_top_classes=3, label="Prediction"),
title="Scenery Classifier",
description="Upload a photo, get a (toy) scene label.",
examples=[["examples/beach.jpg"], ["examples/night.jpg"]],
cache_examples=False,
)
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7861
* To create a public link, set `share=True` in `launch()`.
Rendered UI (verified via /config):
- Markdown title, then description line under it
- left column: Image input (sources: upload, webcam, clipboard)
- right column: Label output "Prediction" (num_top_classes=3)
- bottom: examples dataset with thumbnails for beach.jpg and night.jpg
Event listener: Image.click(Submit) -> api_name="/classify"
Function logic verified: bright input -> {'sky': 0.83, 'cloud': 0.12, 'night': 0.05}; dark input -> {'night': 0.77, 'cave': 0.18, 'forest': 0.05}
The dict-with-confidence-scores return value is the contract gr.Label understands; the component renders each label with a bar. Swap the toy logic for a transformers pipeline and nothing else changes.
import gradio as gr
from PIL import Image
def to_grayscale(img: Image.Image, strength: float):
if img is None:
return None
gray = img.convert("L")
if strength >= 0.99:
return gray
return Image.blend(img.convert("RGB"), gray.convert("RGB"), strength)
demo = gr.Interface(
fn=to_grayscale,
inputs=[
gr.Image(type="pil", label="Photo"),
gr.Slider(0, 1, value=1.0, step=0.05, label="Grayscale strength"),
],
outputs=gr.Image(type="pil", label="Result"),
title="Grayscale Studio",
live=True, # re-runs on every input change, no Submit button
)
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7861 * To create a public link, set `share=True` in `launch()`. Rendered UI (verified via /config): - left column: Image input "Photo" + Slider "Grayscale strength" (min 0, max 1, step 0.05, default 1.0) - right column: Image output "Result" - NO Submit button anywhere — live=True removed it Event listener: targets=[[image, 'change'], [slider, 'change']] inputs=[image, slider] outputs=[image] Function logic verified: strength 1.0 -> mode L, fully gray; strength 0.5 -> pixel blend (147, 72, 72) for input (200, 50, 50)
live=True is the difference between a form and an instrument. Great for filters and sliders, expensive for slow models — every slider tick is a queued run.
| Flag | Meaning |
|---|---|
fn / inputs / outputs | The required trio; list lengths must match the function's parameters and return values, in order. |
examples=[[...], ...] | Clickable sample inputs shown under the app; each inner list is one row, one entry per input component. |
cache_examples=True | Pre-computes example outputs at launch so clicking one is instant; use cache_mode='lazy' to compute on first use instead. |
live=True | Runs fn on every input change and removes the Submit button — ideal for cheap transforms, painful for slow models. |
title / description / article | Markdown above and below the interface; title becomes both the header and the browser tab title. |
flagging_mode | 'manual' (default) shows a Flag button that logs input/output pairs to CSV; 'never' hides it; 'auto' flags every run. |
additional_inputs | Extra components tucked into a collapsed accordion below the main inputs, passed to fn after the primary ones. |
gr.Interface is the API Gradio launched with — version 0.1.0 hit PyPI on 2019-02-19, and the whole library was this one abstraction: point it at a function, get a browser UI. The founders (Abubakar Abid, Ali Abid and team) wanted sharing an ML demo to take minutes, not a web-dev project. Hugging Face acquired Gradio in 2021, and Interface became the default blueprint for thousands of Spaces.
Gradio 3.0 (released 2022) introduced the 'biggest update ever': gr.Blocks, the low-level API for custom layouts and multi-step flows. Yet the team kept Interface as the front door, and the docs still teach it first — because for one-function demos it remains the shortest path from code to URL.
Each component sits between the browser and your fn via two methods: preprocess() turns the incoming JSON into the Python type your function wants (an uploaded image into a PIL object or numpy array, depending on type=), and postprocess() turns the return value back into something the frontend can render (a dict of label->confidence into the Label component's bars). When you call launch(), Interface also emits a /config JSON describing every component and event listener — my probes read the rendered tree straight from it. Clicking Submit joins the queue at /gradio_api/queue/join; the result streams back over SSE. fn never knows a web server exists.