AI/TLDR

What Is assistant-ui? React Components for AI Chat

You will understand what assistant-ui offers as a React component library for AI chat, the primitives it provides, and where a composable chat UI kit fits in your app.

INTERMEDIATE11 MIN READUPDATED 2026-06-14

In plain English

Building an AI chat interface looks easy until you actually do it. You need a message list that scrolls correctly. You need text that streams in token by token without the page jumping around. You need a composer with a send button that disables while the model is thinking. You need to render markdown, code blocks, attachments, and the little widgets a tool call produces. None of this is the interesting part of your app, yet every team rebuilds it from scratch, badly, and then maintains it forever.

assistant-ui — illustration
assistant-ui — mir-s3-cdn-cf.behance.net

assistant-ui is an open-source React component library that hands you all of that plumbing. It gives you composable building blocks — a thread, a message, a composer, a streaming text renderer — that you assemble into the exact chat UI your product needs. You connect it to your model or backend, and it manages the messy runtime concerns: streaming state, auto-scroll, edit-and-resend, attachments, and rendering tool results as real components.

A useful analogy: think of how shadcn/ui changed how people build forms and buttons. Instead of installing a sealed widget you configure from the outside, you copy real, editable components into your project and own them. assistant-ui is often called the "shadcn for chat" for exactly this reason — you compose and own the chat surface rather than dropping in a closed <ChatWidget> you can't reach inside of.

Why it matters

Chat looks like a solved problem and is not. The gap between a demo chat box and a production one is enormous, and almost all of it is UI plumbing nobody wants to own. assistant-ui matters because it closes that gap without locking you into someone else's design.

  • Streaming is genuinely hard. Tokens arrive one at a time over a stream. You have to append them smoothly, keep the view scrolled to the bottom unless the user scrolled up, parse partial markdown that isn't valid yet, and not re-render the entire thread on every chunk. Getting this right is a week of work; getting it subtly wrong shows up as flicker, jank, and lost scroll position.
  • Real chat needs more than text. Production assistants edit and resend messages, branch a conversation, attach files and images, copy code, show a stop button mid-stream, and render the output of tool calls as interactive widgets. Each of these is a small feature that interacts with all the others.
  • A closed widget can't grow with you. Embeddable chat bubbles are quick to drop in but impossible to deeply customize — you can't restyle the message bubble, inject your own component for a tool result, or change the composer's behavior. The moment your design diverges from the vendor's, you're stuck. Owning the components means your chat UI is just your React code.
  • It separates the UI from the brain. assistant-ui is the interface layer. It doesn't care whether the tokens come from your own backend, a hosted model API, or an agent framework — you attach a runtime adapter and the components stay the same. Swap the model behind it and the UI doesn't change.

Who should care? Anyone building a chat-shaped product surface in React: a support assistant, a "chat with your docs" tool, an in-app copilot, an internal agent console. If you're about to write your own message list and streaming loop for the third time, this is the library that question leads to.

How it works

assistant-ui has two halves that snap together: a runtime that owns conversation state, and a set of primitive components that render that state. You pick a runtime (it decides where messages come from and where new ones go), wrap your UI in its provider, then compose the primitives inside.

The runtime: where state lives

The runtime is the source of truth for the thread — the list of messages, who is currently streaming, whether a tool is running, what attachments are pending. You create it with a hook and feed it a runtime adapter that knows how to talk to your backend. Because the adapter is pluggable, the same components work whether your tokens come from your own server route, a hosted model API, or an agent framework. The UI subscribes to the runtime through React context.

The primitives: parts you compose

On top of the runtime sit small, focused components. Thread lays out the scrolling message viewport and handles auto-scroll. Message renders a single turn and exposes its parts (text, attachments, tool calls) as slots you fill. Composer is the input area with send, attach, and cancel actions wired to the runtime. None of them assume a particular look — you bring your own styling (Tailwind, CSS, whatever) and your own components for special content. The library also offers higher-level prebuilt pieces if you just want a working thread fast.

In code, the smallest possible app is just "create a runtime, wrap a provider around the thread." Here is the shape of it — the exact API names vary, but the structure is the durable part:

the basic shapetypescript
import { Thread } from "@assistant-ui/react";
import { useChatRuntime, AssistantRuntimeProvider } from "@assistant-ui/react";

export function ChatApp() {
  // 1) A runtime adapter points at YOUR backend route that streams tokens.
  const runtime = useChatRuntime({ api: "/api/chat" });

  // 2) The provider puts the runtime on React context...
  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* 3) ...and the primitives read from it. Style as you like. */}
      <Thread />
    </AssistantRuntimeProvider>
  );
}

Rendering tool calls as real UI

The feature that pushes assistant-ui past "a nice message list" is how it handles tool calls. When your model calls a tool — say get_weather or create_invoice — you usually don't want to dump the raw JSON result into the chat. You want a widget: a weather card, an invoice preview, a confirm button.

assistant-ui lets you register a React component per tool name. When a message contains a tool call with that name, the library renders your component and passes the tool's arguments and result as props. This is the generative UI pattern, built into the message renderer: the model chooses which tool, and your app owns what that tool looks like on screen.

Because the component is yours, it can be fully interactive — a button that calls back into the runtime, a form the user fills, a chart. The model never builds HTML; it just names a tool and hands over data, and your registered component turns that into a safe, designed widget. A fallback renderer handles any tool you haven't given a component to, so an unknown tool degrades to a readable summary instead of breaking the thread.

assistant-ui vs the alternatives

It helps to place assistant-ui against the other ways people build AI chat. They overlap, but each makes a different trade between speed-to-ship and control.

ApproachWhat you getBest when
assistant-uiComposable React primitives + a runtime; you own and style everythingYou want a custom, production chat UI without writing the streaming plumbing
Embeddable chat widgetA drop-in bubble you configure from the outsideYou need a chatbot fast and don't need deep design control
Hand-rolled from scratchTotal control, total maintenance burdenYour needs are so unusual no library fits — rare
A copilot/agent SDK's built-in UIUI bundled with an agent stackYou've already committed to that stack and accept its UI opinions

The honest summary: assistant-ui sits between the sealed widget and the blank canvas. You skip the weeks of streaming, scrolling, and editing logic, but you keep full ownership of markup and style. The cost is that it's a toolkit, not a finished product — you assemble and design it yourself, which is more work upfront than pasting in a script tag.

When to reach for it (and when not to)

A component library is a commitment, so it's worth being honest about fit. assistant-ui earns its keep on real, evolving chat surfaces and is overkill for a throwaway demo.

Good fits

  • You're building a branded, production chat interface in React and the design must be yours, not a vendor's.
  • You need the hard features — token streaming, edit-and-resend, attachments, stop button, tool-result widgets — and don't want to build them.
  • You expect tool calls to render as interactive components, not raw text.
  • You may swap models or backends later and want the UI to stay put while the runtime changes.

Poor fits

  • You just need a simple support bubble on a marketing site — a hosted widget is faster.
  • Your app isn't React. assistant-ui is React-first; another framework means another tool.
  • You only need a one-off internal demo where polish doesn't matter — plain text in a <div> is fine.

Going deeper

Once the basic thread works, the interesting depth is all in the seams between the runtime and your app. A few directions worth knowing as you build past the starter.

Runtime adapters and where state lives. The choice of runtime adapter is the most consequential one you'll make. A simple adapter streams from one backend route; richer setups manage threads, persistence, and multi-step agent runs. Because the adapter is the boundary, you can start with the simplest one and graduate to a more capable runtime later without rewriting your components — the primitives only ever read from context.

Streaming UX beyond text. Token streaming is table stakes; the polish is in the details the library exposes — partial-markdown rendering that doesn't flash broken syntax, a smart auto-scroll that releases when the user scrolls up, and a visible stop control wired to cancel the in-flight response. These are exactly the things that feel cheap when missing, so they're worth verifying in your app, not just trusting in the demo. The companion concept is designing for LLM latency.

Generative UI as a first-class surface. Registering a component per tool is where chat stops being a transcript and becomes an app. Push on this: components that call back into the runtime, human-in-the-loop approval cards before a tool runs for real, and source/citation widgets that link answers back to documents. The library renders the widget; the UX design is on you.

Treat tool output as untrusted. A widget rendered from a tool result is still rendering data the model influenced. Validate and escape it like any other untrusted input — don't pass raw model output into anything that executes, and keep your registered components strict about the props they accept. The model picking from your fixed catalog of components is what keeps generative UI safe; widen that catalog carefully.

The durable lesson is the one the "shadcn for chat" framing captures: the value isn't a magic chat box, it's that you own composable parts. You get out of the streaming-and-scrolling business while keeping every line of markup under your control — and a chat UI you own is one you can evolve, restyle, and debug, long after any single library's momentum has changed.

FAQ

What is assistant-ui?

assistant-ui is an open-source React component library for building AI chat interfaces. It provides composable primitives — a thread, messages, a composer — plus a runtime that manages streaming and conversation state, so you can build a production chat UI without writing the streaming and scrolling plumbing yourself.

Why is assistant-ui called 'shadcn for chat'?

Because you compose and own the components instead of embedding a sealed widget. Like shadcn/ui, it gives you editable building blocks you assemble and style in your own project, so your chat UI is just your React code — not a closed <ChatWidget> you configure from the outside.

Does assistant-ui include the AI model?

No. It's the interface layer only — it renders a conversation but doesn't generate tokens. You bring your own model or backend and connect it with a runtime adapter. The same components work whether the tokens come from your server, a hosted model API, or an agent framework.

Can assistant-ui render tool calls as custom widgets?

Yes. You can register a React component per tool name, and when a message contains that tool call, the library renders your component with the tool's arguments and result as props. This is the generative-UI pattern built into the message renderer — the model picks the tool, your app owns how it looks.

How is assistant-ui different from an embeddable chat widget?

An embeddable widget is a drop-in bubble you configure from the outside and can't deeply restyle. assistant-ui gives you the underlying primitives so you own the markup, styling, and behavior. It's more setup upfront but doesn't trap you when your design diverges from a vendor's defaults.

Do I need assistant-ui if I'm not using React?

Probably not — assistant-ui is React-first, so a non-React app would need a different tool. It's the right choice specifically when you're building a chat surface in React and want composable, ownable components rather than a closed widget.

Further reading