React UI
Mental model
Understand how @anvia/react controllers, React UI providers, and compound component namespaces fit together.
React UI is easiest to read as a set of context-bound building blocks, not as a closed chat widget.
The dotted names such as Thread.Messages, Composer.Input, and Message.Parts are compound
components. Each namespace groups primitives that share the same controller state and local context.
The shape
@anvia/react owns transport and state reduction. @anvia/react-ui renders that state through
providers and primitives. Your app owns the server route, styling, auth, persistence, and any
domain-specific rendering.
There are two surface families:
- Chat:
useChat(...)->ChatProvider->Thread.*,Composer.*,Message.*, andHumanInput.*. - Completion:
useCompletion(...)->CompletionProvider->Completion.*.
They can share the same wire request shape, but they do not share UI context. Use chat for transcripts, tools, attachments, and human review. Use completion for one prompt and one generated text result.
useChat(...) or useCompletion(...) creates a controller.ChatProvider or CompletionProvider exposes it to UI primitives.Thread.*, Composer.*, and Message.* read chat context; Completion.* reads completion context.{ messages, stream: true } and request metadata.Data flows down from the hook controller into providers and primitives. User actions flow back up
through controller methods such as sendMessage, stop, regenerate, approval handlers, or
question handlers.
How to read dotted components
The first word is the context family. The second word is the primitive rendered inside that family.
| Pattern | Meaning |
|---|---|
ChatProvider |
Makes a useChat(...) controller available to chat primitives. |
Thread.Root |
Creates thread context around the transcript area and emits thread state. |
Thread.Messages |
Iterates chat.messages and installs message context for each row. |
Message.Root |
Reads the current message and emits message attributes such as data-role. |
Message.Parts |
Iterates message.parts and installs part context for each part. |
Composer.Root |
Creates composer context for draft text, pending attachments, submit, and stop. |
Composer.Input |
Reads and writes the nearest composer draft. |
HumanInput.Panel |
Reads pending approvals and questions from the chat controller. |
The same pattern applies to CompletionProvider and Completion.*, except completion surfaces use
a useCompletion(...) controller instead of a chat controller.
CompletionProvider does not provide chat contexts. If a surface needs Thread, Composer,
Message, tools, attachments, or human input, use useChat(...) and ChatProvider.
The common chat tree
Start with the smallest tree that lets the package render messages for you:
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>
);
Thread.Messages has no child function here, so it uses the default row and part renderer. This is
the preferred starting point because new message part types can render without application changes.
Rendering levels
Move down one level only when the product needs control at that level.
| Level | You keep | You customize |
|---|---|---|
| Default list | Thread.Messages |
CSS through stable data-anvia-* and data-role attributes. |
| Message row | Message.Parts |
Row layout, avatars, actions, timestamps, copy, regenerate. |
| Message part | Message.Part fallback |
Tool cards, Markdown, attachment previews, structured data. |
| Route request | UI primitives | Model selectors, metadata, auth, persistence, and tool execution. |
For example, customize the row but keep default part rendering:
<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>
Customize individual parts only when one part type needs product-specific UI. Keep the fallback:
<Message.Parts>
{(part) => {
if (part.type === "tool") {
return (
<Message.Part>
<Message.Tool className="tool-card" renderWhen="always" />
</Message.Part>
);
}
return <Message.Part />;
}}
</Message.Parts>
Boundaries to remember
- The browser renders UI and calls your API route. It should not hold provider credentials or run provider SDKs directly.
@anvia/react-uidoes not create a controller. It expects auseChat(...)oruseCompletion(...)result from@anvia/react.- Use
createRequestonuseChat(...)for request-level options such as model, tenant, or reasoning effort. UseComposer.RootsubmitMessageonly when the submitted message itself needs custom text, attachments, or metadata. - Prefer the default
Thread.MessagesandMessage.Partfallbacks until a product-specific part needs custom markup. - Style stable attributes such as
data-anvia-message,data-role,data-part, anddata-stateinstead of relying on internal DOM details.
Next steps
- Cheat sheet: grab imports, skeletons, renderer rules, and common snippets.
- Getting started: render the first chat surface.
- Request shape: add model selectors and metadata.
- Messages: choose the right renderer level.
- Styling: target stable attributes from application CSS.
