Message

One chat row per role — alignment, surface, and consecutive-row grouping all derive from a single from prop.

Which clients still need their annual review?
Three reviews are due this month: Hargreaves, Patel, and Osei.
Conversation moved to the Hargreaves client file
<Message from="user">  <MessageContent>Which clients still need their annual review?</MessageContent></Message><Message from="assistant">  <MessageContent>Three reviews are due this month: Hargreaves, Patel, and Osei.</MessageContent></Message><Message from="system">  <MessageContent>Conversation moved to the Hargreaves client file</MessageContent></Message>

Usage

import { Message, MessageContent } from "@reva/ui";

Everything derives from from. The markup is identical for every role — a MessageContent (plus any below-content chrome — an Actions bar and a timestamp, arranged by MessageFooter) — and the role drives alignment and surface. Rows are deliberately avatar-less and quiet, so a conversation reads as a document rather than a support chat:

  • user — right-aligned, quiet neutral bubble (surface/subtle-solid).
  • assistant — left-aligned and unwrapped — no card, no padding; the reply reads as page content at full row width.
  • system — full-width, de-emphasized small text. For error rows, compose an Alert inside MessageContent (see the example below).

Rows must be direct siblings in a no-gap container

Grouping and rhythm are pure sibling CSS: each row adds mt-6 after a role change and mt-2 after a same-role row. That assumes message rows are direct siblings in a container without its own gapConversationContent is exactly that, and a VStack gap={0} works standalone. A gap-* on the list would stack with the rows' margins.

Rows enter with a small fade and rise (reduced-motion safe, AnimatePresence-compatible). When hydrating an existing history — like the seeded demos on the Conversation page — pass initial={false} so old messages don't replay their entrance.

Composition

Use the following composition to build a Message:

Message                  (from="user" | "assistant" | "system")
├── MessageContent       (plain text, or composed parts: Response, CodeBlock, Sources, …)
└── MessageFooter?       (optional below-content row — space-between)
    ├── Actions          (copy, regenerate, vote, …)
    └── MessageTimestamp  (the send / generated time)

MessageContent is the family's composition seam — it renders whatever parts you compose: a Response, a Code Block, Sources, a Loader, or plain text. MessageFooter is the below-content row that pushes an Actions bar and a MessageTimestamp to opposite edges; MessageTimestamp locks the quiet trailing-meta voice (caption1, regular weight, muted ink) so timestamps never drift in style.

Examples

Consecutive-role grouping

Same-role rows group automatically — tight mt-2 gap after a same-role row, mt-6 after a role change. No state, no list-walking: the row never knows about its siblings, the previous-sibling CSS does.

Summarise the Patel SIPP position.
Include the bond allocation, please.
The Patel SIPP holds £182,400 across four funds, up 3.1% this quarter.
Bonds sit at 30.8% against a 35% target — 4.2% under, just outside tolerance.
<Message from="user">…</Message><Message from="user">…</Message>      {/* groups: mt-2 */}<Message from="assistant">…</Message> {/* new group: mt-6 */}<Message from="assistant">…</Message>

Below an assistant reply, wrap the Actions bar and a MessageTimestamp in MessageFooterjustify-between pushes them to opposite edges. Under a user message, drop a bare MessageTimestamp: the row's right-alignment carries it under the bubble.

<Message from="assistant">
  <MessageContent>Three reviews are due this month: Hargreaves, Patel, and Osei.</MessageContent>
  <MessageFooter>
    <Actions>{/* copy, regenerate, vote */}</Actions>
    <MessageTimestamp>Jun 13, 1:29pm</MessageTimestamp>
  </MessageFooter>
</Message>

<Message from="user">
  <MessageContent>Which clients still need their annual review?</MessageContent>
  <MessageTimestamp>Jun 13, 1:28pm</MessageTimestamp>
</Message>

System error row

System rows are the slot for conversation-level notices. For failures, compose the house Alert inside MessageContent — the row stays full-width and the Alert brings its own status semantics.

Regenerate that fee summary.
<Message from="system">  <MessageContent>    <Alert status="destructive">      <AlertIcon />      <AlertTitle>Reply failed — the model timed out. Try regenerating.</AlertTitle>    </Alert>  </MessageContent></Message>

Accessibility

  • Message rows carry no live-region semantics of their own — announcing new rows is the scroll shell's job. Inside Conversation, ConversationContent is the role="log" polite live region; that page's Accessibility section covers how additions and streamed text are announced.
  • Rows are avatar-less by design — the author is conveyed by the role-driven alignment and surface (right-aligned bubble vs. unwrapped left column), so no decorative imagery needs hiding from assistive tech.
  • Under reduced motion the entrance fade/rise is skipped (values only — the markup never forks). The initial / animate / exit / transition overrides are trusted as passed, so keep overrides reduced-motion-aware.
  • System rows are plain de-emphasized text with no implicit status semantics — for failures, compose the house Alert inside MessageContent (example above) and it brings its own role.

Props

ComponentPropTypeDefaultDescription
Messagefrom"user" | "assistant" | "system"Author role — drives alignment, surface, and grouping. Required.
Messageinitial / animate / exit / transitionMotion propsfade + rise on the house springOverride the entrance motion; initial={false} for hydrated history.
MessageContentdiv propsThe composition seam — renders whatever parts you compose.
MessageFooterdiv propsBelow-content row; flex + justify-between to push Actions (left) and the timestamp (right) apart.
MessageTimestampText props (minus textStyle / weight / color)The send / generated time. Locks the caption1 / regular / muted recipe; pass the formatted string as children.

On this page