Skip to content

ChatMessageList Component ​

Renders a list of messages with auto-scroll, the streaming reply and the progress of tool calls.

New in 0.4.0

Cards for paused turns, a live log role, "Show earlier messages", the interrupted-reply notice, and the built-in button row.

Import ​

tsx
import { ChatMessageList } from "@cognipeer/chat-ui";

Usage ​

tsx
const chat = useChat({ baseUrl: "/api/agents", agentId: "assistant" });

<ChatMessageList
  messages={chat.messages}
  isStreaming={chat.isStreaming}
  streamingText={chat.streamingText}
  progressMessage={chat.progressMessage}
  activeToolCalls={chat.activeToolCalls}
  actions={{ onResolve: chat.resolveAction, canResolve: chat.canResolveActions }}
/>

Props ​

PropTypeDefaultDescription
messagesMessage[]requiredArray of messages
isStreamingbooleanfalseA reply is streaming
streamingTextstring-Current streaming text
progressMessagestring-Progress line shown while waiting
activeToolCallsMap-Tool calls of the running turn
actionsChatActionsConfig-How approval / question cards behave. Without onResolve they are read-only ("Waiting for approval in another app"); see Approvals and Questions
autoScrollbooleantrueScroll to new messages
showAvatars, showTimestampsbooleantrue, falseMessage chrome
enableCitationsbooleantrueSources under answers
renderActions(props: MessageActionProps) => ReactNode-Custom action buttons
renderAvatar(role) => ReactNode-Custom avatar
renderImage(props: ImageRenderProps) => ReactNode-Draw images yourself
renderStopNotice(props: StopNoticeRenderProps) => ReactNode-Replace the notice under an answer that stopped early
resolveFileUrl(file, { disposition? }) => Promise<string | undefined>-How stored files without a URL are shown and downloaded; pass chat.resolveFileUrl
hasEarlier, isLoadingEarlier, onLoadEarlierboolean, boolean, () => voidfalse, false, -A "Show earlier messages" button above the first message; pass chat.hasEarlierMessages, chat.isLoadingEarlierMessages and chat.loadEarlierMessages. The list keeps the reader's place when the older page arrives
interrupted, onRetryInterruptedboolean, () => voidfalse, -The list ends on a message of the person's that the run never answered: a "The reply was interrupted" notice follows it, with Retry when onRetryInterrupted is given; pass chat.interrupted and chat.retryInterrupted
toolbarListToolbarConfig-The built-in buttons under messages: { copy, regenerate?, edit?, feedback?: { comments, reasons, onSubmit } }. Copy shows on every message with text; regenerate only under the newest answer; edit on the person's own messages; thumbs on answers. Chat builds it from its portal props
canActbooleanfalseRegenerate and edit change the conversation: pass true only while nothing runs and no card waits
aiLabelstring-Label every answer with this text
emptyStateReactNodebuilt-inShown when there are no messages
classNamestring-Container class name

Examples ​

Basic ​

tsx
const messages = [
  { id: "1", conversationId: "c", role: "user", content: "Hello", createdAt: new Date(), updatedAt: new Date() },
  { id: "2", conversationId: "c", role: "assistant", content: "Hi there!", createdAt: new Date(), updatedAt: new Date() },
];

<ChatMessageList messages={messages} />

With Streaming ​

tsx
<ChatMessageList messages={messages} isStreaming streamingText="I'm typing a response..." />

Custom Empty State ​

tsx
<ChatMessageList
  messages={[]}
  emptyState={
    <div className="empty-state">
      <h3>Welcome to Chat!</h3>
      <p>Start a conversation by sending a message.</p>
    </div>
  }
/>

With Actions ​

tsx
<ChatMessageList
  messages={messages}
  renderActions={({ message, isStreaming }) => {
    if (isStreaming || message.role !== "assistant") return null;
    return (
      <div>
        <button>Copy</button>
        <button>Feedback</button>
      </div>
    );
  }}
/>

Auto-Scroll Behavior ​

With autoScroll the list scrolls to the bottom when the messages, the streaming text or the tool calls change.

Accessibility ​

The list is a labelled live log. While a reply streams it is aria-busy, so a screen reader reads the finished answer once instead of every chunk. When an approval or question card appears in a conversation that is already on screen, a hidden assertive live region announces it and the first new card takes keyboard focus (unless the person is typing). Cards that arrive with a loaded conversation are not announced and do not take focus.

Virtual Scrolling ​

For very large message lists, render ChatMessage inside a virtual list instead:

tsx
import { FixedSizeList } from "react-window";

<FixedSizeList height={600} itemCount={messages.length} itemSize={100}>
  {({ index, style }) => (
    <div style={style}>
      <ChatMessage message={messages[index]} />
    </div>
  )}
</FixedSizeList>

Styling ​

The list scrolls with the .chat-scrollbar styles from styles.css; colours come from the theme variables. See Theming.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub