gr.ChatInterface — a full chat UI in one line

One function + one line = a chatbot UI. It is Gradio's highest-leverage shortcut.

The UI that powered thousands of HuggingFace chat Spaces takes ONE line of Python to create — but its fn signature trips almost everyone.

What it does

gr.ChatInterface(fn=...) builds a complete chat web app around your function: a Chatbot panel, a message Textbox, Submit and Stop buttons, a Clear button, autoscroll, and a queue — all wired together. Your fn receives (message, history) and returns the assistant reply as a string. History is a list of message dicts ({'role': ..., 'content': ...}). Set multimodal=True and the input becomes a MultimodalTextbox so users can attach files; the message arrives as a dict {'text': ..., 'files': [...]}.

Why it matters

Chat is the interface people expect from LLM products now. ChatInterface gives you the polished default — streaming replies, retry, undo, copy buttons, auto-generated API endpoint — without writing a single event listener. When you outgrow it (custom message rendering, side panels, login), drop down to gr.Blocks and keep everything else.

Examples

import gradio as gr

def echo(message, history):
    return f"You said: {message}"

demo = gr.ChatInterface(fn=echo, title="Echo Bot")
demo.launch()

# fn signature: (message: str, history: list) -> str
# gr.ChatInterface.__init__ has 35 parameters in Gradio 6.28.0
Console:
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

Rendered UI (verified by introspecting demo.blocks — 15 components):
Column
 ├─ Markdown (title "Echo Bot")
 ├─ Chatbot (the message thread, empty at start)
 └─ Group
     ├─ Row
     │   └─ Textbox ("Type a message...", round pill, Enter-to-send)
     │   └─ Form (submit + stop buttons rendered together)
     ├─ Textbox (hidden input, part of the wiring)
     ├─ Button (submit, arrow icon)
     └─ JSON (hidden internal state holder)

Typing "hi" and hitting Enter appends your message bubble on the right,
then "You said: hi" appears in an assistant bubble on the left.

Bonus — call it without a browser via the REST API it auto-exposes:
POST /gradio_api/call/echo {"data": ["hi", []]}
→ event: complete\ndata: ["You said: hi", null]

The fn gets (message, history) and returns ONE string — that's the whole contract.

import gradio as gr

def streamy(message, history):
    for word in ["alpha ", "beta ", "gamma"]:
        yield word

demo = gr.ChatInterface(fn=streamy)
demo.launch()

# Same fn, streaming: yield instead of return
Console:
* Running on local URL:  http://127.0.0.1:7860

Rendered UI: identical 14-component tree (no examples Dataset component this time) —
Column > Markdown (title) > Chatbot > Group > Row > Textbox + Button.

The reply bubble types out word by word: "alpha ", then "beta ", then "gamma".
Gradio auto-detected the generator function and switched to streaming mode.

Same call over the REST API shows the progressive stream:
POST /gradio_api/call/streamy {"data": ["one two three", []]}
→ event: generating\ndata: ["alpha ", null]
→ event: generating\ndata: ["beta ", null]
→ event: generating\ndata: ["gamma", null]
→ event: complete\ndata: ["gamma", null]

Any generator fn streams for free — no extra parameters, no queue config needed.

import gradio as gr

def vision_chat(message, history):
    files = [f.split("/")[-1] for f in message.get("files", [])]
    return f"Got text={message.get('text')!r}, files={files}"

demo = gr.ChatInterface(
    fn=vision_chat,
    multimodal=True,
    title="Vision Helper",
)
demo.launch()

# message is a dict: {"text": str, "files": [filepath, ...]}
Console:
* Running on local URL:  http://127.0.0.1:7860

Rendered UI (15 components): same skeleton as the basic app, but the input
Row now holds a MultimodalTextbox — a message field with a 📎 attach button.

Drag an image called photo.webp into the box, type "what is this?", send:
your bubble shows the image preview + text; the assistant bubble replies:
"Got text='what is this?', files=['photo.webp']"

multimodal=False (the default) keeps the plain single-line Textbox.

multimodal=True changes the message type from str to dict — code both branches or pin the format.

import gradio as gr

def greet(message, history, system_prompt):
    return f"[{system_prompt}] {message}"

demo = gr.ChatInterface(
    fn=greet,
    additional_inputs=[
        gr.Textbox("You are helpful.", label="System prompt"),
    ],
    examples=[["hello", "You are terse."], ["who are you", "You are HAL."]],
)
demo.launch()

# extra fn args come from additional_inputs, AFTER (message, history)
# with additional_inputs, examples must be lists of lists
Console:
* Running on local URL:  http://127.0.0.1:7860

Rendered UI (18 components — the extra inputs add 4):
Column > Markdown > Chatbot > Group > Row > Textbox + Button > JSON
plus:
 ├─ Accordion ("Additional Inputs", collapsed) > Textbox ("System prompt")
 └─ Dataset (the 2 clickable example chips under the input)

Expand the accordion, set the system prompt to "You are terse.",
type "hello" and send: assistant bubble shows "[You are terse.] hello".
Clicking the "who are you" example chip fills the message AND switches
the system prompt value to "You are HAL." before sending.

Examples become a Dataset of clickable chips; with additional_inputs each example row must include a value for every extra input.

Flags

FlagMeaning
fnYour chat function: (message, history) -> str | generator yielding str chunks.
multimodalTrue swaps the Textbox for a MultimodalTextbox; message becomes {'text': ..., 'files': [...]} (introduced in 4.22.0).
additional_inputsExtra components passed to fn after (message, history); rendered inside an Accordion under the chat.
chatbotPass your own gr.Chatbot(...) to control height, avatar images, or placeholder before the first message.
textboxPass your own gr.Textbox(...) or gr.MultimodalTextbox(...) to customize placeholder, lines, or initial value.
examplesClickable starter prompts rendered as chips; must be a list of lists when additional_inputs are present.
editableFalse by default; True lets users edit a sent message before resubmitting.

Born in 3.37.0 (July 2023)

ChatInterface shipped as an exported class in Gradio 3.37.0 (PyPI upload 2023-07-17). Back then history was a list of (user, bot) tuples — the dict {'role': ..., 'content': ...} format only became the norm in the 4.x line. I verified 3.36.1 has no ChatInterface in gradio/__init__.py and 3.37.0 does, straight from the wheels on PyPI.

From tuples to dicts to built-in

multimodal=True arrived in 4.22.0 (I bisected the wheels: 4.21.0 has no multimodal param, 4.22.0 does). In 5.x the Chatbot switched fully to the messages format; in 6.x ChatInterface dropped its own 'type' parameter and just speaks messages dicts. The pattern is Gradio's whole history in one component: a demo-first convenience that later hardens into the standard.

Under the hood: it IS a Blocks app

ChatInterface subclasses gr.Blocks and constructs its own components in __init__: a Column with a Markdown header, a Chatbot, a Group holding the Textbox and submit/stop Buttons, plus State/BrowserState objects tracking conversation state. It then wires .submit on the Textbox and .click on the buttons to an internal method that appends the user message, calls your fn, and streams the reply back into the Chatbot. That's why len(demo.blocks) == 15 for a bare ChatInterface — and why you can reach demo.chatbot to customize the underlying Chatbot after construction.

Fun facts

Pros

Cons

Takeaways