React UI
Cheat sheet
Quick reference for @anvia/react-ui providers, namespaces, snippets, and common decisions.
Use this page when you already understand the mental model and need the shortest path to the right primitive or snippet.
Install and import
pnpm add @anvia/react @anvia/react-ui
import { useChat } from "@anvia/react";
import {
ChatProvider,
Composer,
type ComposerQuote,
type ComposerTriggerDefinition,
HumanInput,
Image,
Message,
SelectionToolbar,
Thread,
ThreadList,
ThreadListItem,
ThreadListProvider,
} from "@anvia/react-ui";
import "@anvia/react-ui/styles.css";
For completion surfaces:
import { useCompletion } from "@anvia/react";
import { Completion, CompletionProvider } from "@anvia/react-ui";
Chat or completion
| Choose | When | Do not use for |
|---|---|---|
| Chat | Multi-turn transcript, follow-up prompts, tools, attachments, suggestions, or human review. | One-shot prompt panels with no transcript. |
| Completion | One prompt input and one generated text output. | Tool cards, attachments, human review, or message threads. |
Both can send { messages, stream: true } to an API route. The difference is the client state and
UI context: chat exposes messages and chat actions; completion exposes prompt input and output text.
Pick the right primitive
| Need | Use | Must live inside |
|---|---|---|
| Multi-turn transcript | useChat(...) |
React component state |
| Chat UI context | ChatProvider |
Receives controller={chat} |
| Scrollable transcript | Thread.Root, Thread.Viewport |
ChatProvider |
| Message list | Thread.Messages |
Thread.Root is recommended |
| Message row and parts | Message.Root, Message.Parts, Message.Part |
Thread.Messages |
| Prompt input | Composer.Root, Composer.Input |
ChatProvider |
| Mentions, slash commands, variables | Composer.Root, Composer.Input, Composer.TriggerMenu |
ChatProvider |
| Pending attachments | Composer.Attachments, Attachment.* |
Composer.Root |
| Image attachment preview | Image.* |
Message.Attachment, Attachment.Root, or explicit attachment |
| Quote selected message text | SelectionToolbar.*, Composer.Quote |
Thread.Root and controlled Composer.Root quote state |
| Conversation history list | ThreadList.*, ThreadListItem.* |
ThreadListProvider |
| Tool cards | Message.Tool |
Message.Part for a tool part |
| Human review | HumanInput.* |
ChatProvider with humanInput configured |
| One-shot text generation | useCompletion(...), Completion.* |
CompletionProvider |
Minimal chat
import { useChat } from "@anvia/react";
import { ChatProvider, Composer, Thread } from "@anvia/react-ui";
import "@anvia/react-ui/styles.css";
export function ChatSurface() {
const chat = useChat({ endpoint: "http://localhost:8787/api/chat" });
return (
<ChatProvider controller={chat}>
<Thread.Root>
<Thread.Viewport autoScroll>
<Thread.Empty>Ask your first question.</Thread.Empty>
<Thread.Messages />
<Thread.Error />
<Thread.ScrollToBottom>Latest</Thread.ScrollToBottom>
</Thread.Viewport>
<Composer.Root>
<Composer.Attachments />
<Composer.Input placeholder="Message Anvia..." />
<Composer.Stop>Stop</Composer.Stop>
<Composer.Submit>Send</Composer.Submit>
</Composer.Root>
</Thread.Root>
</ChatProvider>
);
}
Keep <Thread.Messages /> empty until you need product-specific rows or parts.
Composer triggers
Use triggers for @ mentions, / commands, $ variables, or another app-owned catalog.
const triggers: ComposerTriggerDefinition[] = [
{
id: "people",
char: "@",
items: [{ id: "user_ada", label: "Ada Lovelace", data: { type: "user" } }],
},
{
id: "commands",
char: "/",
startOfLine: true,
items: [{ id: "summarize", label: "Summarize", text: "/summarize" }],
},
];
const chat = useChat({ endpoint: "http://localhost:8787/api/chat" });
return (
<ChatProvider controller={chat}>
<Composer.Root triggers={triggers}>
<Composer.Input placeholder="Try @ or /..." />
<Composer.TriggerMenu />
<Composer.Submit />
</Composer.Root>
</ChatProvider>
);
Selected trigger values are submitted as metadata.composer.entities. Use
Composer.TextareaInput for native textarea behavior without inline entities. For the full API, see
Composer triggers.
Server route shape
The default hook request sends core messages to your API route:
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",
});
}
Add request options
Use createRequest for model selectors, tenant ids, reasoning effort, feature flags, or any option
that belongs to the whole request.
import type { Message } from "@anvia/core/completion";
import { useChat } from "@anvia/react";
type ChatRequest = {
messages: Message[];
stream: true;
metadata: {
model: string;
reasoningEffort: "low" | "medium" | "high";
};
};
const chat = useChat<ChatRequest>({
endpoint: "http://localhost:8787/api/chat",
createRequest: ({ coreMessages }) => ({
messages: coreMessages,
stream: true,
metadata: {
model,
reasoningEffort,
},
}),
});
Use Composer.Root submitMessage only when the submitted message itself needs custom text,
attachments, or per-message metadata.
Images, quotes, and thread history
Use image primitives inside attachment rendering:
<Message.Attachment>
<Image.Root>
<Image.ZoomTrigger>
<Image.Preview />
</Image.ZoomTrigger>
<Image.Name />
<Image.Actions>
<Image.Copy />
<Image.Download />
</Image.Actions>
<Image.ZoomOverlay />
</Image.Root>
</Message.Attachment>
Use selection quotes as controlled composer state:
const [quote, setQuote] = useState<ComposerQuote>();
<SelectionToolbar.Root onQuote={setQuote} />
<Composer.Root quote={quote} onQuoteChange={setQuote}>
<Composer.Quote />
<Composer.ClearQuote />
<Composer.Input />
<Composer.Submit />
</Composer.Root>
Use thread-list primitives with an app-owned controller:
<ThreadListProvider controller={threadList}>
<ThreadList.Root>
<ThreadList.New />
<ThreadList.Items>
<ThreadListItem.Root>
<ThreadListItem.Trigger>
<ThreadListItem.Title />
</ThreadListItem.Trigger>
</ThreadListItem.Root>
</ThreadList.Items>
</ThreadList.Root>
</ThreadListProvider>
For a complete recipe, see Images, selection, and thread list.
Renderer ladder
| Start here | Move here when | Snippet |
|---|---|---|
| Default list | Default parts are enough | <Thread.Messages /> |
| Custom row | You need avatars, actions, timestamps, or layout | Thread.Messages child function |
| Custom part | One part type needs product-specific UI | Message.Parts child function |
| Custom route | The server request shape is product-specific | useChat({ createRequest }) |
Custom row, default parts:
<Thread.Messages>
{(message) => (
<Message.Root className="message-row">
<Message.Content>
<Message.Parts />
</Message.Content>
<Message.Actions>
<Message.Copy>Copy</Message.Copy>
<Message.Regenerate>Try again</Message.Regenerate>
</Message.Actions>
</Message.Root>
)}
</Thread.Messages>
Custom part, fallback for everything else:
<Message.Parts>
{(part) => {
if (part.type === "text") {
return (
<Message.Part>
<Message.Markdown />
</Message.Part>
);
}
return <Message.Part />;
}}
</Message.Parts>
Tools
Tools execute on the server. The browser renders streamed tool parts.
<Message.Parts>
{(part) =>
part.type === "tool" ? (
<Message.Part>
<Message.Tool className="tool-card" renderWhen="always">
<Message.ToolName />
<Message.ToolStatus />
<Message.ToolInput />
<Message.ToolOutput />
<Message.ToolError />
</Message.Tool>
</Message.Part>
) : (
<Message.Part />
)
}
</Message.Parts>
Useful renderWhen values:
| Value | Shows |
|---|---|
always |
Input, output, and errors whenever available. |
pending |
input-streaming and input-available. |
settled |
output-available and error. |
Human input
Configure the review endpoint on useChat(...):
const chat = useChat({
endpoint: "http://localhost:8787/api/chat",
humanInput: {
endpoint: "http://localhost:8787/api/review",
},
});
Then render a review panel anywhere inside ChatProvider:
<HumanInput.Panel className="review-panel">
<HumanInput.Status />
<HumanInput.Approvals />
<HumanInput.Questions />
</HumanInput.Panel>
Use keepMounted for persistent lanes:
<aside className="review-lane">
<HumanInput.Status />
<HumanInput.Approvals keepMounted />
<HumanInput.Questions keepMounted />
</aside>
Attachments
Use the built-in picker:
<Composer.Root>
<Composer.Attachments />
<Composer.AddAttachment accept="image/*,.pdf" multiple>
Attach
</Composer.AddAttachment>
<Composer.Input />
<Composer.Submit />
</Composer.Root>
Use a dropzone when the composer region should accept dropped files:
<Composer.Root>
<Composer.AttachmentDropzone className="dropzone">
<Composer.Attachments />
<Composer.Input />
<Composer.Submit />
</Composer.AttachmentDropzone>
</Composer.Root>
Completion
Use completion for one prompt and one generated text result:
import { useCompletion } from "@anvia/react";
import { Completion, CompletionProvider } from "@anvia/react-ui";
export function CompletionPanel() {
const completion = useCompletion({
endpoint: "http://localhost:8787/api/complete",
});
return (
<CompletionProvider controller={completion}>
<Completion.Root>
<Completion.Output />
<Completion.Form>
<Completion.Input placeholder="Draft a product update..." />
<Completion.Stop>Stop</Completion.Stop>
<Completion.Submit>Generate</Completion.Submit>
</Completion.Form>
</Completion.Root>
</CompletionProvider>
);
}
Use chat instead when the surface needs a transcript, tools, attachments, or human review.
Styling targets
Style stable attributes from app CSS or Tailwind selectors:
| Attribute | Emitted by | Use |
|---|---|---|
data-anvia-thread |
Thread.Root |
Overall chat state. |
data-anvia-thread-viewport |
Thread.Viewport |
Scroll container state. |
data-anvia-message |
Message.Root |
Message row. |
data-role |
Message.Root |
user, assistant, system, or tool. |
data-anvia-part |
Message.Part |
Per-part layout. |
data-part |
Message.Part |
Part type. |
data-anvia-tool |
Message.Tool |
Tool-call cards. |
data-anvia-composer |
Composer.Root |
Composer state. |
data-state |
Many primitives | idle, streaming, error, enabled, disabled, etc. |
Defaults to keep
- Keep
<Thread.Messages />until you need custom rows. - Keep
<Message.Part />as the fallback when branching on part types. - Keep request-level options in
createRequest. - Keep server-only work, provider credentials, tools, auth, and persistence in the API route.
- Keep final layout and visual design in application CSS or your design system.
