Quote

Let users select and quote text from messages with a floating toolbar, composer preview, and inline quote display.

User message

The runtime system follows a layered architecture

Can you explain how the layers connect?

Selection Toolbar
Quote
Composer Preview
The runtime system follows a layered architecture
Send a message...

Getting Started

Add quote

npx shadcn@latest add https://r.assistant-ui.com/quote.json

This adds a /components/assistant-ui/quote.tsx file with three components: QuoteBlock, SelectionToolbar, and ComposerQuotePreview.

Display Quotes in User Messages

Pass QuoteBlock to MessagePrimitive.Parts as the Quote renderer:

components/assistant-ui/thread.tsx
import { QuoteBlock } from "@/components/assistant-ui/quote";

const UserMessage = () => {
  return (
    <MessagePrimitive.Root>
      <MessagePrimitive.Parts
        components={{
          Quote: QuoteBlock,
        }}
      />
    </MessagePrimitive.Root>
  );
};

Add the Floating Selection Toolbar

Render SelectionToolbar inside ThreadPrimitive.Root. It portals to the document body and appears near the user's text selection.

components/assistant-ui/thread.tsx
import { SelectionToolbar } from "@/components/assistant-ui/quote";

const Thread = () => {
  return (
    <ThreadPrimitive.Root>
      <ThreadPrimitive.Viewport>
        <ThreadPrimitive.Messages components={{ ... }} />
        ...
      </ThreadPrimitive.Viewport>

      <SelectionToolbar />
    </ThreadPrimitive.Root>
  );
};

Show Quote Preview in Composer

Add ComposerQuotePreview inside your composer. It only renders when a quote is set.

components/assistant-ui/thread.tsx
import { ComposerQuotePreview } from "@/components/assistant-ui/quote";

const Composer = () => {
  return (
    <ComposerPrimitive.Root>
      <ComposerQuotePreview />
      <ComposerPrimitive.Input placeholder="Send a message..." />
      <ComposerPrimitive.Send />
    </ComposerPrimitive.Root>
  );
};

Forward Quote Context to the LLM

Quote data is stored in message metadata, not in message content. Use injectQuoteContext in your route handler so the LLM sees the quoted text:

app/api/chat/route.ts
import { convertToModelMessages, streamText } from "ai";
import { injectQuoteContext } from "@assistant-ui/react-ai-sdk";

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: myModel,
    messages: await convertToModelMessages(injectQuoteContext(messages)),
  });

  return result.toUIMessageStreamResponse();
}

injectQuoteContext prepends the quoted text as a markdown > blockquote to the message parts so the LLM receives the context the user is referring to.

For alternative backend approaches (Claude SDK citation source, OpenAI message context), see the Quoting guide.

Customization

All three components expose sub-components for full control over styling:

// Custom QuoteBlock
<QuoteBlock.Root className="my-custom-class">
  <QuoteBlock.Icon />
  <QuoteBlock.Text>{text}</QuoteBlock.Text>
</QuoteBlock.Root>

// Custom SelectionToolbar
<SelectionToolbar.Root>
  <SelectionToolbar.Quote>Reply with quote</SelectionToolbar.Quote>
</SelectionToolbar.Root>

// Custom ComposerQuotePreview
<ComposerQuotePreview.Root>
  <ComposerQuotePreview.Icon />
  <ComposerQuotePreview.Text />
  <ComposerQuotePreview.Dismiss />
</ComposerQuotePreview.Root>

API Reference

QuoteBlock

Renders quoted text in user messages. Pass to MessagePrimitive.Parts as components.Quote.

PropTypeDescription
textstringThe quoted text
messageIdstringID of the source message

Sub-components: QuoteBlock.Root, QuoteBlock.Icon, QuoteBlock.Text

SelectionToolbar

Floating toolbar that appears when text is selected within a message. Renders as a portal positioned above the selection.

PropTypeDescription
classNamestringAdditional class names

Sub-components: SelectionToolbar.Root, SelectionToolbar.Quote

ComposerQuotePreview

Quote preview inside the composer. Only renders when a quote is set.

PropTypeDescription
classNamestringAdditional class names

Sub-components: ComposerQuotePreview.Root, ComposerQuotePreview.Icon, ComposerQuotePreview.Text, ComposerQuotePreview.Dismiss

injectQuoteContext

import { injectQuoteContext } from "@assistant-ui/react-ai-sdk";

injectQuoteContext(messages: UIMessage[]): UIMessage[]

Extracts metadata.custom.quote from each message and prepends the quoted text as a > blockquote text part. Use before convertToModelMessages in your route handler. For alternative backend approaches, see the Quoting guide.

useMessageQuote

import { useMessageQuote } from "@assistant-ui/react";

const quote: QuoteInfo | undefined = useMessageQuote();

Returns the quote attached to the current message, or undefined. Useful for building custom quote displays without QuoteBlock. For a usage example, see the Quoting guide.

ComposerRuntime.setQuote

setQuote(quote: QuoteInfo | undefined): void

Set or clear the quote on the composer programmatically. The quote is automatically cleared when the message is sent. For a usage example, see the Quoting guide.

  • Quoting guide: Backend handling, programmatic API, data shape, and design notes
  • Thread: Main chat container