AI Conversation

Compose a reader-aware conversation viewport with messages and explicit copy feedback.

UIUpdated October 16, 2026

Compose your conversationLink to section

AIConversation displays a scrollable, labelled message list. It follows new content while the reader is at the end and preserves their position when they scroll back. Your application supplies messages and transport; this component makes no model requests.

Package availability

AIConversation, AIMessage and AIMessageActions are introduced by the conversation primitives minor Changeset. Use the repository workspace until that release is published. They are not exported by @pycolors/ui@1.6.1 or earlier.

Follow the reader

New content follows the viewport only while the reader remains at the end.

Return explicitly

Jump to latest returns to the end and restores focus to the conversation viewport.

Keep application ownership

Supply message content, model transport, persistence and clipboard callbacks yourself.

PreviewLink to section

Local conversation example
  1. You

    How can I make the next step clearer for the person using this interface?
  2. Assistant

    Start with one clear outcome and keep recovery visible. This is a fixed sample response.
  3. You

    How can I make the next step clearer for the person using this interface?
  4. Assistant

    Start with one clear outcome and keep recovery visible. This is a fixed sample response.
  5. You

    How can I make the next step clearer for the person using this interface?
  6. Assistant

    Start with one clear outcome and keep recovery visible. This is a fixed sample response.

Scroll up, then add a message: your reading position stays put. Choose Jump to latest to resume following. No model is connected.

The example runs here in the documentation. The Explorer link appears when this story is available in the published catalog; otherwise use this inline preview.

Controlled compositionLink to section

conversation.tsx
"use client";

import { AIConversation, AIMessage, AIMessageActions } from "@pycolors/ui";

type Message = { id: string; author: string; text: string };

export function Conversation({ messages }: { messages: Message[] }) {
  return (
    <AIConversation
      label="Support conversation"
      messagesLabel="Support messages"
    >
      {messages.map((message) => (
        <AIMessage
          key={message.id}
          author={message.author}
          actions={
            <AIMessageActions
              value={message.text}
              copyAriaLabel={`Copy ${message.author} message`}
              onCopy={(text) => navigator.clipboard.writeText(text)}
            />
          }
        >
          {message.text}
        </AIMessage>
      ))}
    </AIConversation>
  );
}

Place AIComposer after the conversation. Keep request errors, retry and cancellation in your consumer, or use the AI Chat Block for the complete composed surface. This component does not parse Markdown or HTML.

Behavior and accessibilityLink to section

Supply semantic messagesLink to section

Children belong to an ordered list. Render AIMessage or native li elements, with stable keys. The empty state belongs outside that list. Use a meaningful label and messagesLabel when more than one conversation appears on the page.

Respect reading positionLink to section

The focusable viewport follows content and resize changes while at the end. Scrolling more than 32 pixels away pauses following. Returning near the end or choosing Jump to latest resumes it. Layout changes do not force a reader back to the bottom. Without ResizeObserver, React child updates still follow.

Announce request state in your applicationLink to section

The message list is not a live region: streaming tokens must not repeatedly interrupt a screen reader. Provide concise request-status announcements outside the list. Jumping uses immediate scrolling and restores viewport focus without animated motion. Keyboard and screen-reader verification remain part of your application's integration checks.

APILink to section

PropDefaultPurpose
children—Semantic list items, usually AIMessage instances
labelConversationAccessible viewport name
messagesLabelMessagesAccessible ordered-list name
jumpLabelJump to latestVisible return button label
viewportClassName—Override the default max-h-[28rem] viewport styling
className, native div props—Outer wrapper attributes and styling
ref—Outer HTMLDivElement, not the inner viewport

Slots: ai-conversation and ai-conversation-viewport. Caller classes use the standard PyColors cn() precedence. The viewport owns its scroll handling and focus behavior. Use a stable component key to reset an entire conversation.