# Quote Selected Text URL: /docs/guides/quoting Let users select and quote text from messages. Full guide including backend handling and programmatic API. Get Started \[#get-started] The \*\*[Quote](/docs/ui/quote)\*\* registry component gives you everything you need out of the box, including a floating selection toolbar, composer quote preview, and inline quote display. It ships three composable pieces: * **QuoteBlock** renders quoted text inline in user messages * **SelectionToolbar** is a floating toolbar that appears on text selection * **ComposerQuotePreview** shows the pending quote inside the composer See the [Quote component page](/docs/ui/quote) for full setup steps and API reference. *** How It Works \[#how-it-works] When a user selects text in an assistant message, a floating toolbar appears with a Quote button. Clicking it calls `composer.setQuote()` to store the selection on the composer. The [Quote component](/docs/ui/quote) does this out of the box. When the message is sent, the composer runtime automatically writes the quote to `message.metadata.custom.quote` and clears it from the composer. On the backend, the route handler extracts the quote from metadata and surfaces it to the LLM. We export a helper called `injectQuoteContext` that handles this automatically for AI-SDK. Without this step, the quote appears in the UI but is not sent to the model as context. See [Backend Handling](#backend-handling) for more info and alternatives. 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 ``` Backend Handling \[#backend-handling] Quote data travels in message metadata, not content, so the LLM will not see it unless your backend extracts and surfaces it. The simplest path is [`injectQuoteContext`](/docs/ui/quote#forward-quote-context-to-the-llm), which prepends quoted text as a markdown blockquote before the message parts. For provider-specific handling, work with the quote metadata directly. Claude SDK Citations \[#claude-sdk-citations] Pass the quoted text as a citation source so Claude produces citations that reference it: ```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(); 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-6", max_tokens: 1024, messages: claudeMessages, }); // ... stream response back } ``` OpenAI Message Context \[#openai-message-context] 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}`, }; }); } ``` Reading Quote Data \[#reading-quote-data] Use `useMessageQuote` to access quote data in custom components: ```tsx import { useMessageQuote } from "@assistant-ui/react"; function CustomQuoteDisplay() { const quote = useMessageQuote(); if (!quote) return null; return (
{quote.text}
); } ``` Programmatic API \[#programmatic-api] Set or clear quotes 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 ( <> ); } ``` Design Notes \[#design-notes] * **Single quote:** `setQuote` replaces the current quote instead of appending. Only one quote can be active at a time. * **Snapshot text:** The selected text is captured when the quote is created and is not linked to the source message afterward. * **Cross-message selection:** The toolbar only appears when the selection stays within a single message. * **Streaming messages:** The toolbar still works while a message is streaming because it relies on the captured selection rather than message status. * **`isEmpty` unchanged:** A quote by itself does not make the composer non-empty. The user still needs to type a reply. * **Scroll hides toolbar:** The toolbar hides on scroll because its position would otherwise become stale. Related \[#related] * [Quote component](/docs/ui/quote): Installation, component setup, and API reference * [Message Editing](/docs/guides/editing): Edit user messages * [Thread](/docs/ui/thread): Main chat container