ConversationList

Follows new messages at the end, preserving position during scrolling and history loading.

import { ConversationList } from "@usefragments/ui";Source

Preview

const reply = "Two files need updated imports.";

<ConversationList>
  <Message from="user">
    <Message.Content>Which files break the workspace rules?</Message.Content>
  </Message>

Usage

Use when

  • Chat interfaces holding many messages
  • Transcripts that should follow new arrivals
  • Paging back through older message history
  • Runs of messages grouped day by day

Avoid when

  • Plain item lists with no chat context (use List)
  • A single message (use Message directly)
  • Layouts that never scroll

Implementation notes

  • Give the log a parent with a definite height; it fills it and scrolls
  • autoScroll="smart" follows only while the reader is at the end; away from it, new messages are counted under the log
  • Implement onScrollTop and set history while earlier messages load; the reader's place holds
  • ConversationList.Event marks a day break (date) or something that happened (a model switch)
  • Put ThinkingIndicator last while the reply has not started
  • Give new conversations an emptyState

Installation

pnpm add @usefragments/ui

The npm package stays managed by your package manager. Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import ConversationList once, then compose its parts with dot notation.

  • ConversationList.Event

Examples

With Events

A day break and a model switch.

const yesterday = new Date(2026, 9, 3);

<ConversationList>
  <ConversationList.Event date={yesterday} />
  <Message from="user">
    <Message.Content>Check main.</Message.Content>

Thinking

The indicator sits last until the reply starts.

const waiting = true;
const messages = [<Message key="question" from="user"><Message.Content>Check main.</Message.Content></Message>];

<ConversationList>
  {messages}
  {waiting && <ThinkingIndicator label="Reading the request…" />}

Loading History

Earlier messages load at the top; the reader's place holds.

const loading = true;
const loadEarlier = () => {};
const messages = [<Message key="question" from="user"><Message.Content>Check main.</Message.Content></Message>];

<ConversationList onScrollTop={loadEarlier} history={loading ? "loading" : "idle"}>
  {messages}

History Failed

A failed load says so and offers Retry.

const loadEarlier = () => {};
const messages = [<Message key="question" from="user"><Message.Content>Check main.</Message.Content></Message>];

<ConversationList history="error" onRetryHistory={loadEarlier}>
  {messages}
</ConversationList>

Empty

A first-run state when there are no messages.

<ConversationList
  emptyState={<EmptyState><EmptyState.Title>Ask about your design system</EmptyState.Title></EmptyState>}
>
  {[]}
</ConversationList>

API reference

Component props
Prop
Type
Default
Description
autoScrollAutoScrollBehavior | undefinedsmartsmart follows new content while the reader is at the end; false never moves the reader
childrenRequiredReact.ReactNodeNot setMessages, events and the thinking indicator
emptyStateReact.ReactNodeNot setWhat shows when there are no messages
Event.childrenReact.ReactNodeNot setWhat happened (a model switch, a joined user). Wins over `date`.
Event.dateDate | undefinedNot setA date for a day break; formatted as Today, Yesterday or the date.
Event.format((date: Date) => string) | undefinedNot setCustom date format.
historyConversationHistory | undefinedidleloadingerroridleWhere earlier messages stand: idle, loading (a status line) or error (an errbox with Retry)
labelstring | undefinedConversationAccessible name of the log
onRetryHistory(() => void) | undefinedNot setRetry loading earlier messages after history=error
onScrollTop(() => void) | undefinedNot setCalled when the reader reaches the top, to load earlier messages
onScrollUIEventHandler<HTMLDivElement> | undefinedNot set—

Accessibility

  • The log is role=log with a name and polite announcements
  • The scroller takes keyboard focus and draws its ring inside the edge
  • Loading history is a status; a failed load is an errbox with a labelled Retry
  • The jump button names what it does: N new messages or Jump to latest
  • Event hairlines are decorative; dates are time elements
  • MessagechildConversationList contains Message components
  • ThinkingIndicatorchildShow ThinkingIndicator at bottom while awaiting response
  • PromptsiblingTypically paired with Prompt for input

Next steps