# 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