Message

Displays user or assistant conversation turns, including sending, streaming and failure states.

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

Preview

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

Usage

Use when

  • Individual turns in a chat transcript
  • AI assistant and chatbot interfaces
  • User and assistant turns that must read differently
  • Turns carrying copy, regenerate, or feedback actions

Avoid when

  • Plain text with no chat context (use Text)
  • Notifications (use Alert or Toast)
  • Threads with nested replies (use Card with a custom layout)

Implementation notes

  • Always set from; it places the turn and picks the reading
  • Events such as a model switch or a joined user are ConversationList.Event, not a message
  • A string from the assistant renders as markdown; from the user it keeps its line breaks
  • status covers pending, streaming and error; error adds the failure block with Try again when onRetry is set
  • No avatars by default; pass avatar when a conversation has more than two people
  • The user's bubble caps at 80% of the measure so the two sides stay distinct
  • Show one waiting pattern: ThinkingIndicator before the reply starts, the caret while it streams

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 Message once, then compose its parts with dot notation.

  • Message.Content
  • Message.Actions
  • Message.Timestamp
  • Message.Avatar
  • Message.Error

The user's words in a band bubble at the end, capped at 80% of the measure.

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

Examples

Assistant Message

The reply is flush and renders markdown.

<Message from="assistant">
  <Message.Content>{"Two files use a raw `button`."}</Message.Content>
</Message>

Streaming

A still caret at the end while the reply arrives.

const partial = "Two files need updated imports…";

<Message from="assistant" status="streaming">
  <Message.Content>{partial}</Message.Content>
</Message>

Sending

The meta line says Sending… until the message lands.

<Message from="user" status="pending">
  <Message.Content>Summarise the changes on main.</Message.Content>
</Message>

Failed

The failure block says what happened and offers Try again.

const partial = "The reply was interrupted.";
const retry = () => {};

<Message from="assistant" status="error" onRetry={retry}>
  <Message.Content>{partial}</Message.Content>
</Message>

With Timestamp

A relative time in the meta line.

const sentAt = new Date(2026, 9, 4, 10, 30);

<Message from="user" timestamp={sentAt}>
  <Message.Content>Thanks!</Message.Content>
</Message>

With Actions

Copy and regenerate beside the meta line.

import { Copy } from "@phosphor-icons/react";

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

<Message
  from="assistant"

With Avatar

Opt in when more than two people talk.

<Message from="user" avatar={<Message.Avatar>AL</Message.Avatar>}>
  <Message.Content>Can you check main?</Message.Content>
</Message>

API reference

Component props
Prop
Type
Default
Description
actionsReact.ReactNodeNot setActions for the message (copy, regenerate), shown on hover or focus where a pointer hovers, always on touch
Actions.childrenRequiredReact.ReactNodeNot set—
avatarReact.ReactNodeNot setOpt-in avatar slot, usually Message.Avatar. No avatar is drawn by default
Avatar.altstring | undefinedNot setAlt text for the image.
Avatar.childrenReact.ReactNodeNot setInitials or a glyph when there is no image.
Avatar.srcstring | undefinedNot setImage URL.
childrenRequiredReact.ReactNodeNot setMessage parts, usually Message.Content
Content.childrenRequiredReact.ReactNodeNot setA string from the assistant renders as markdown; from the user it keeps its line breaks.
Error.actionsReact.ReactNodeNot setMore actions beside retry (for example "Ask differently").
Error.childrenReact.ReactNodeNot setWhat went wrong.
Error.onRetry(() => void) | undefinedNot setRetry handler; adds the "Try again" action. Defaults to the message's `onRetry`.
Error.retryLabelstring | undefinedNot setLabel of the retry action.
fromRequiredMessageFromuserassistantNot setWho wrote the message: the user's bubble sits at the end, the assistant's reply is flush
onRetry(() => void) | undefinedNot setRetry a failed message; the failure block offers Try again
statusMessageStatus | undefinedpendingerrorstreamingcompletecompleteWhere the message stands: pending shows Sending…, streaming a still caret at the end, error a failure block
timestampDate | undefinedNot setWhen the message was sent; shown in the meta line
Timestamp.dateDate | undefinedNot setDate to show. Defaults to the message's `timestamp`.
Timestamp.format((date: Date) => string) | undefinedNot setCustom format function.

Accessibility

  • A streaming message sets aria-busy until it completes
  • The failure block is an alert with its words and a labelled Try again
  • Actions are reachable by keyboard and shown on focus; they are always visible on touch
  • The timestamp is a time element with a machine-readable date
  • ConversationListparentMessages are typically used within ConversationList
  • AvatarchildUse Avatar component for custom avatar content
  • ThinkingIndicatorsiblingShow ThinkingIndicator while awaiting assistant response

Next steps