Skip to content

ChatMessageList Component

Renders a list of messages with auto-scroll.

Import

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

Usage

tsx
<ChatMessageList
  messages={messages}
  isLoading={isLoading}
  streamingText={streamingText}
/>

Props

PropTypeDescription
messagesMessage[]Array of messages
isLoadingbooleanWhether loading/streaming
streamingTextstringCurrent streaming text
streamingMessagePartial<Message>Streaming message data
activeToolCallsMapActive tool calls
renderMessageFunctionCustom message renderer
renderToolCallFunctionCustom tool call renderer
renderActionsFunctionCustom action buttons
renderEmptyFunctionEmpty state renderer
classNamestringContainer class name

Examples

Basic

tsx
const messages = [
  { id: "1", role: "user", content: "Hello", createdAt: "..." },
  { id: "2", role: "assistant", content: "Hi there!", createdAt: "..." },
];

<ChatMessageList messages={messages} />

With Streaming

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

Custom Message Rendering

tsx
<ChatMessageList
  messages={messages}
  renderMessage={({ message, isStreaming, streamingText }) => (
    <div className={`message ${message.role}`}>
      <Avatar role={message.role} />
      <div className="content">
        {isStreaming ? streamingText : message.content}
      </div>
    </div>
  )}
/>

Custom Empty State

tsx
<ChatMessageList
  messages={[]}
  renderEmpty={() => (
    <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

The list automatically scrolls to:

  • New messages
  • Streaming content updates
  • Bottom when sending a message

Scroll is paused when user scrolls up.

Virtual Scrolling

For large message lists, consider wrapping in a virtual list:

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

css
.chat-message-list {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
}

.chat-message-list::-webkit-scrollbar {
  width: 8px;
}

.chat-message-list::-webkit-scrollbar-thumb {
  background: var(--chat-border-primary);
  border-radius: 4px;
}

Released under the MIT License.