Docs

React UI

Usage patterns

Common ways to compose @anvia/react-ui with @anvia/react and application-owned UI.

Pair controllers with providers

@anvia/react-ui expects controllers from @anvia/react.

  • Use useChat(...) with ChatProvider, Thread, Message, Composer, and HumanInput.
  • Use useCompletion(...) with CompletionProvider and Completion.
  • Keep server routes, auth, persistence, and model selection outside the UI package.
import { useChat } from "@anvia/react";
import { ChatProvider, Composer, Thread } from "@anvia/react-ui";

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

return (
  <ChatProvider controller={chat}>
    <Thread.Root>
      <Thread.Viewport>
        <Thread.Messages />
      </Thread.Viewport>
      <Composer.Root>
        <Composer.Input />
        <Composer.Submit />
      </Composer.Root>
    </Thread.Root>
  </ChatProvider>
);

The API app route behind http://localhost:8787/api/chat should accept the React wire shape described in Server routes.

Stay headless

Every primitive accepts regular element props such as className. Button-like primitives also support asChild, so applications can attach Anvia behavior to design-system components.

<Composer.Submit asChild>
  <IconButton aria-label="Send message" icon={<ArrowUpIcon />} />
</Composer.Submit>

Use data-anvia-*, data-role, and data-state selectors when styling should follow primitive state instead of component names.

[data-anvia-message][data-role="user"] {
  justify-items: end;
}

[data-anvia-submit][data-state="disabled"] {
  opacity: 0.5;
}

Start default, then specialize

The default message renderer is useful longer than it first looks. Keep it for ordinary parts, and branch only for product-specific tools, attachment layouts, or Markdown components.

import { Message, Thread } from "@anvia/react-ui";

export function Messages() {
  return (
    <Thread.Messages className="messages">
      {(message) => (
        <Message.Root className="message">
          <Message.Content className="message-content">
            <Message.Parts>
              {(part) => {
                if (part.type === "tool" && part.toolName === "searchDocs") {
                  return <SearchDocsTool part={part} />;
                }

                if (part.type === "attachment") {
                  return (
                    <Message.Part className="attachment-part">
                      <Message.Attachment className="attachment-preview" />
                    </Message.Part>
                  );
                }

                return <Message.Part />;
              }}
            </Message.Parts>
          </Message.Content>
        </Message.Root>
      )}
    </Thread.Messages>
  );
}

Attach files in the composer

Composer.AddAttachment opens a file picker, Composer.AttachmentInput gives direct access to the underlying input, Composer.AttachmentDropzone accepts dropped files, and Composer.Attachments renders pending attachments before the prompt is sent.

import { Composer } from "@anvia/react-ui";

export function AttachmentComposer() {
  return (
    <Composer.Root className="composer">
      <Composer.AttachmentDropzone className="dropzone">
        <Composer.Attachments className="attachment-list" />
        <Composer.AddAttachment accept="image/*,.pdf" multiple>
          Attach
        </Composer.AddAttachment>
        <Composer.Input minRows={1} maxRows={6} />
        <Composer.Submit />
      </Composer.AttachmentDropzone>
    </Composer.Root>
  );
}

Add app-owned controls

Keep model selectors, reasoning effort, temperature, provider choice, and similar controls in application state. Render them inside Composer.Root when they belong in the prompt bar, then pass their values through useChat({ createRequest }). Use Composer.Root submitMessage only when the message payload itself needs customization, such as per-message metadata.

See Composer controls for a complete recipe.

const chat = useChat({
  endpoint: "http://localhost:8787/api/chat",
  createRequest: ({ coreMessages }) => ({
    messages: coreMessages,
    stream: true,
    metadata: {
      model,
      reasoningEffort,
    },
  }),
});

Control composer state

Keep the composer uncontrolled for simple chat surfaces. Use controlled props when the draft or pending attachments need to sync with application state.

const [draft, setDraft] = useState("");
const [attachments, setAttachments] = useState<UIAttachment[]>([]);

return (
  <Composer.Root
    input={draft}
    onInputChange={setDraft}
    attachments={attachments}
    onAttachmentsChange={setAttachments}
  >
    <Composer.Attachments keepMounted />
    <Composer.AttachmentInput multiple />
    <Composer.Input />
    <Composer.Submit />
  </Composer.Root>
);

For restored conversations and server history, see Persistence.

Control empty wrappers

Some collection primitives unmount when empty so they do not create layout gaps. Use keepMounted when your app CSS needs a stable wrapper.

<Thread.Messages keepMounted={false} />
<Thread.Suggestions keepMounted />
<Composer.Attachments keepMounted />
<HumanInput.Approvals keepMounted />
<HumanInput.Questions keepMounted />