Docs

React UI

Images, selection, and thread list

Add image attachment rendering, quote-from-selection, and an app-owned thread list to @anvia/react-ui chat surfaces.

Use these primitives when a chat surface needs richer message affordances without giving up application-owned state.

  • Image.* renders an image attachment with preview, actions, and zoom.
  • SelectionToolbar.* reacts to selected message text and can copy or quote it.
  • ThreadList.* renders app-owned conversation history. It does not persist threads by itself.

Image attachments

Image.Root reads the current Attachment context, so it works naturally inside Message.Attachment.

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

<Thread.Messages>
  <Message.Root>
    <Message.Content>
      <Message.Parts>
        {(part) =>
          part.type === "attachment" ? (
            <Message.Part>
              <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>
            </Message.Part>
          ) : (
            <Message.Part />
          )
        }
      </Message.Parts>
    </Message.Content>
  </Message.Root>
</Thread.Messages>;

Use the existing Attachment.* primitives for document/file rows. Use Image.* when the attachment is an image and needs image-specific actions.

Quote selected text

SelectionToolbar.Root appears only when the browser selection is fully inside one Message.Root. Bridge its onQuote callback into controlled Composer.Root quote state.

import { useState } from "react";
import type { ComposerQuote } from "@anvia/react-ui";
import { Composer, SelectionToolbar, Thread } from "@anvia/react-ui";

export function QuoteChatControls() {
  const [quote, setQuote] = useState<ComposerQuote>();

  return (
    <Thread.Root>
      <Thread.Viewport>
        <Thread.Messages />
      </Thread.Viewport>

      <SelectionToolbar.Root onQuote={setQuote}>
        <SelectionToolbar.Quote />
        <SelectionToolbar.Copy />
      </SelectionToolbar.Root>

      <Composer.Root quote={quote} onQuoteChange={setQuote}>
        <Composer.Quote />
        <Composer.ClearQuote />
        <Composer.Input />
        <Composer.Submit />
      </Composer.Root>
    </Thread.Root>
  );
}

With the default submit behavior, quoted text is prefixed into the user message as a Markdown blockquote and the UI message metadata receives { quote: { text, messageId } }. Use submitMessage if your route needs a different wire shape.

App-owned thread list

ThreadListProvider receives a controller from your application. The controller can be backed by local state, router state, IndexedDB, a server API, or your Studio/session storage.

import { ThreadList, ThreadListItem, ThreadListProvider } from "@anvia/react-ui";

<ThreadListProvider controller={threadList}>
  <ThreadList.Root className="conversation-list">
    <ThreadList.New>New chat</ThreadList.New>

    <ThreadList.Items>
      <ThreadListItem.Root>
        <ThreadListItem.Trigger>
          <ThreadListItem.Title fallback="New chat" />
        </ThreadListItem.Trigger>
        <ThreadListItem.Archive />
        <ThreadListItem.Delete />
      </ThreadListItem.Root>
    </ThreadList.Items>

    <ThreadList.Empty>No conversations yet.</ThreadList.Empty>
  </ThreadList.Root>
</ThreadListProvider>;

Controller shape:

type ThreadListController = {
  threads: ThreadListRecord[];
  activeThreadId?: string;
  status?: "idle" | "loading" | "error";
  error?: unknown;
  createThread(): Promise<void> | void;
  switchThread(threadId: string): Promise<void> | void;
  archiveThread?(threadId: string): Promise<void> | void;
  unarchiveThread?(threadId: string): Promise<void> | void;
  deleteThread?(threadId: string): Promise<void> | void;
};

Use ThreadList.Items archived for archived conversations. Active rows emit data-active, and ThreadList.Root supports ArrowUp, ArrowDown, Home, and End focus movement between item triggers.