Docs

React UI

Getting started

Install @anvia/react-ui, wire it to useChat, and render a styled chat surface.

Install the UI package beside the React hook package:

pnpm add @anvia/react @anvia/react-ui

If you do not already have a React app and API route, start with TanStack quickstart. This page focuses on the UI composition once a compatible API endpoint exists.

Route requirement

The endpoint is the API app URL, not a frontend route. For local development:

const chat = useChat({ endpoint: "http://localhost:8787/api/chat" });

That call posts the default React request body:

type UIStreamRequest = {
  messages: Message[];
  stream: true;
  metadata?: JsonValue;
};

The route should read body.messages and return an Anvia stream:

import type { UIStreamRequest } from "@anvia/core";
import { createEventStream } from "@anvia/server";

export async function POST(request: Request) {
  const body = (await request.json()) as UIStreamRequest;

  return createEventStream(agent.prompt(body.messages).stream(), {
    format: "jsonl",
  });
}

Build the first chat

@anvia/react owns the useChat(...) controller. @anvia/react-ui reads that controller through ChatProvider and renders the thread, messages, suggestions, errors, scrolling, attachments, and composer controls.

import { useChat } from "@anvia/react";
import { ChatProvider, Composer, Thread } from "@anvia/react-ui";
import "@anvia/react-ui/styles.css";

export function SupportChat() {
  const chat = useChat({
    endpoint: "http://localhost:8787/api/chat",
    suggestions: [
      { id: "summarize", label: "Summarize", prompt: "Summarize this thread." },
      { id: "next", label: "Next step", prompt: "What should I do next?" },
    ],
  });

  return (
    <ChatProvider controller={chat}>
      <Thread.Root className="chat">
        <Thread.Viewport className="chat-scroll">
          <Thread.Empty className="empty-state">Start a conversation.</Thread.Empty>
          <Thread.Suggestions className="suggestions" />
          <Thread.Messages className="messages" />
          <Thread.Error className="thread-error" />
          <Thread.ScrollToBottom className="scroll-button">Latest</Thread.ScrollToBottom>
        </Thread.Viewport>

        <Composer.Root className="composer">
          <Composer.Attachments className="composer-attachments" />
          <Composer.AddAttachment>Attach</Composer.AddAttachment>
          <Composer.Input placeholder="Ask a question..." maxRows={6} />
          <Composer.Stop>Stop</Composer.Stop>
          <Composer.Submit>Send</Composer.Submit>
        </Composer.Root>
      </Thread.Root>
    </ChatProvider>
  );
}

What happened

The example uses the default message renderer through <Thread.Messages />. That means the package renders every supported UIMessagePart for you: text, reasoning, tool calls, attachments, data, and errors.

The app still owns:

  • The API app route at /api/chat.
  • The API app origin and CORS policy.
  • Authentication and persistence.
  • Page layout and final visual styling.
  • Custom message, tool-call, or attachment components when the defaults are not enough.

Next steps