# Quote Selected Text URL: /docs/guides/quoting Let users select and quote text from messages, similar to Claude's quoting experience. Allow users to select text in assistant messages and reply with a quote reference — just like Claude, ChatGPT, and other modern AI interfaces. How It Works \[#how-it-works] 1. User selects text in a message 2. A floating toolbar appears near the selection with a **Quote** button 3. User clicks it — a quote preview appears in the composer 4. User types their reply and sends 5. The sent message displays the quoted text above the user's reply Quote data is stored in `metadata.custom.quote` and is **not** injected into message content. Your backend decides how to present the quoted context to the LLM. Quick Start \[#quick-start] 1. Add the Floating Selection Toolbar \[#1-add-the-floating-selection-toolbar] Place `SelectionToolbarPrimitive` inside your `ThreadPrimitive.Root`. It renders a floating toolbar near the user's text selection — only when text is selected within a message. ```tsx {1,13-20} import { SelectionToolbarPrimitive, ThreadPrimitive } from "@assistant-ui/react"; import { QuoteIcon } from "lucide-react"; const Thread = () => { return ( ... {/* Floating toolbar — appears on text selection */} Quote ); }; ``` The `Root` component: * Listens for `mouseup` and `keyup` events to detect text selections * Validates the selection is within a single message (cross-message selections are ignored) * Renders a portal positioned above the selection * Prevents `mousedown` from clearing the selection when clicking the toolbar * Hides automatically on scroll or when the selection is cleared 2. Show Quote Preview in Composer \[#2-show-quote-preview-in-composer] Add `ComposerPrimitive.Quote` inside the composer to show what's being quoted: ```tsx {1,8-13} import { ComposerPrimitive } from "@assistant-ui/react"; const Composer = () => { return ( {/* Quote preview — only renders when a quote is set */} × ); }; ``` 3. Display Quotes in Sent Messages \[#3-display-quotes-in-sent-messages] Use `useMessageQuote()` to render quoted text in user messages: ```tsx {1,4-5,11} import { MessagePrimitive, useMessageQuote } from "@assistant-ui/react"; const QuoteBlock = () => { const quote = useMessageQuote(); if (!quote) return null; return (
{quote.text}
); }; const UserMessage = () => { return ( ); }; ``` Backend Handling \[#backend-handling] The quote is stored in message metadata — **not** in message content. This gives your backend full control over how to present quoted context to the LLM. Data Shape \[#data-shape] ```typescript type QuoteInfo = { readonly text: string; // selected plain text readonly messageId: string; // source message ID }; // Stored at: message.metadata.custom.quote ``` Example: Prepend as Markdown Blockquote \[#example-prepend-as-markdown-blockquote] A simple approach is to prepend the quoted text as a `>` blockquote before converting to model messages: ```typescript title="app/api/chat/route.ts" {1,9} import { convertToModelMessages, streamText } from "ai"; import type { UIMessage } from "ai"; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: myModel, messages: await convertToModelMessages(injectQuoteContext(messages)), }); return result.toUIMessageStreamResponse(); } function injectQuoteContext(messages: UIMessage[]): UIMessage[] { return messages.map((msg) => { const quote = (msg.metadata as Record)?.custom; if ( !quote || typeof quote !== "object" || !("quote" in (quote as Record)) ) return msg; const q = (quote as Record).quote; if ( !q || typeof q !== "object" || !("text" in (q as Record)) ) return msg; const text = (q as { text: unknown }).text; if (typeof text !== "string") return msg; return { ...msg, parts: [{ type: "text" as const, text: `> ${text}\n\n` }, ...msg.parts], }; }); } ``` Example: Claude-Style Citation Source \[#example-claude-style-citation-source] For Claude's API, you can pass the quoted text as a citation source. This enables Claude to produce citations that reference the quoted text: ```typescript title="app/api/chat/route.ts" import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); export async function POST(req: Request) { const { messages } = await req.json(); // Transform messages: extract quotes into Claude source blocks const claudeMessages = messages.map((msg) => { const quote = msg.metadata?.custom?.quote; if (!quote?.text) { return { role: msg.role, content: extractText(msg) }; } return { role: "user", content: [ { type: "text", text: quote.text, cache_control: { type: "ephemeral" }, citations: { enabled: true }, }, { type: "text", text: extractText(msg), }, ], }; }); const response = await client.messages.create({ model: "claude-sonnet-4-5-20250929", max_tokens: 1024, messages: claudeMessages, }); // ... stream response back } ``` Example: OpenAI-Style System Context \[#example-openai-style-system-context] For OpenAI's API, inject the quote as additional context in the user message: ```typescript title="app/api/chat/route.ts" function injectQuoteForOpenAI(messages) { return messages.map((msg) => { const quote = msg.metadata?.custom?.quote; if (!quote?.text || msg.role !== "user") return msg; return { ...msg, content: `[Referring to: "${quote.text}"]\n\n${msg.content}`, }; }); } ``` Programmatic API \[#programmatic-api] You can set or clear quotes programmatically via the composer runtime: ```tsx import { useAui } from "@assistant-ui/react"; function MyComponent() { const aui = useAui(); const quoteText = () => { aui.thread().composer.setQuote({ text: "The text to quote", messageId: "msg-123", }); }; const clearQuote = () => { aui.thread().composer.setQuote(undefined); }; return ( <> ); } ``` API Reference \[#api-reference] SelectionToolbarPrimitive.Root \[#selectiontoolbarprimitiveroot] A floating container that appears when text is selected within a message. Renders as a portal positioned above the selection. * Listens for `mouseup` / `keyup` to detect selection * Validates selection is within a single message * Hides on scroll or when selection is cleared * Prevents `mousedown` from clearing the selection * Provides selection context to child components SelectionToolbarPrimitive.Quote \[#selectiontoolbarprimitivequote] A button inside the floating toolbar that captures the selection as a quote. * Reads selection info from the toolbar context (not `window.getSelection()`) * Stores `{ text, messageId }` in the thread composer * Clears the text selection after quoting ComposerPrimitive.Quote \[#composerprimitivequote] A container that only renders when a quote is set. ComposerPrimitive.QuoteText \[#composerprimitivequotetext] Renders the quoted text. Defaults to ``. ComposerPrimitive.QuoteDismiss \[#composerprimitivequotedismiss] A button that clears the quote by calling `setQuote(undefined)`. Supports `asChild`. useMessageQuote() \[#usemessagequote] ```tsx const quote: QuoteInfo | undefined = useMessageQuote(); ``` Returns the quote attached to the current message, or `undefined`. ComposerRuntime.setQuote() \[#composerruntimesetquote] ```tsx setQuote(quote: QuoteInfo | undefined): void ``` Set or clear the quote on the composer. The quote is automatically cleared when the message is sent. Design Notes \[#design-notes] * **Single quote** — `setQuote` replaces, not appends. Only one quote at a time. * **Snapshot text** — The selected text is captured at quote time, not linked to the source message. * **Cross-message selection** — Rejected. The toolbar only appears when the selection is within a single message. * **Streaming messages** — The floating toolbar works during streaming since it reads from the captured selection, not message status. * **`isEmpty` unchanged** — A quote alone doesn't make the composer non-empty. The user must type a reply. * **Scroll hides toolbar** — The floating toolbar hides when any scroll event occurs, since the position would become stale. Related \[#related] * [Message Editing](/docs/guides/editing) — Edit user messages * [Thread Component](/docs/ui/thread) — Main chat container * [ComposerPrimitive](/docs/reference/primitives/Composer) — Composer primitive reference * [ActionBarPrimitive](/docs/reference/primitives/ActionBar) — Action bar primitive reference