Message
Atomic chat turn with role-aware layout, attachments, branch navigation and a streaming markdown surface.
Overview
Message renders a single chat turn. It picks layout, alignment and surface treatment from the from role ("user" vs "assistant"), then lets you compose the inside with content, attachments, a toolbar and branch navigation. The streaming markdown is rendered by MessageResponse, which is memoized to keep large turns smooth while tokens stream in.
Use it as the atomic unit of any conversation: one Message per turn, composed with siblings like Reasoning, Sources or InlineCitation.
Preview
Assistant with toolbar
How can I help?
I can answer questions, summarize documents, and walk through code with you. Pick a task:
- Explain a concept
- Refactor a function
- Draft an email
Just let me know what you need.
User message
Rich markdown response
Here's the breakdown:
- Speed — caching layer responds in under 50ms.
- Coverage — 100 primitives shipped across 9 packages.
- DX —
npx gremorie add rx-messageis the single entry point.
The registry is now the source of truth.
User and assistant pair
Gremorie is an AI-native design system distributed via registry and MCP. It ships React and Angular primitives that AI agents can pull on demand.
Installation
bash npx gremorie@latest add rx-message bash pnpm dlx gremorie@latest add rx-message bash yarn dlx gremorie@latest add rx-message bash bunx --bun gremorie@latest add rx-message Usage
import {
Message,
MessageContent,
MessageResponse,
} from "@gremorie/rx-ai";
export function Example() {
return (
<Message from="assistant">
<MessageContent>
<MessageResponse>{markdown}</MessageResponse>
</MessageContent>
</Message>
);
}import { Component } from "@angular/core";
import {
Message,
MessageContent,
MessageResponse,
} from "@gremorie/ng-ai";
@Component({
selector: "app-example",
standalone: true,
imports: [Message, MessageContent, MessageResponse],
template: ` <message from="assistant">
<message-content>
<message-response>{{ markdown }}</message-response>
</message-content>
</message>
`,
})
export class ExampleComponent {
readonly markdown = "Hello! How can I help you today?";
}
API
<Message>
| Prop | Type | Default | Description |
|---|---|---|---|
from | "user" | "assistant" | "system" | - | Role of the turn. Drives alignment and surface treatment via is-user / is-assistant group classes. |
className | string | - | Extra classes on the root div. |
Extends all HTMLAttributes<HTMLDivElement>.
<MessageContent>
Container for the turn body. Picks up styling from the parent Message via group selectors (e.g. user turns get a rounded secondary surface).
Extends all HTMLAttributes<HTMLDivElement>.
<MessageResponse>
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | - | Markdown source. Re-renders are gated by string equality. |
Wraps Streamdown for incremental token-by-token rendering.
<MessageAttachments> / <MessageAttachment>
| Prop | Type | Default | Description |
|---|---|---|---|
data | FileUIPart | - | Attachment payload (filename, mediaType, url). |
onRemove | () => void | - | Optional remove handler. Renders an X button on hover. |
<MessageToolbar> / <MessageActions> / <MessageAction>
| Prop | Type | Default | Description |
|---|---|---|---|
tooltip | string | - | Tooltip label rendered above the action. Doubles as sr-only text. |
label | string | - | Explicit accessible label (overrides tooltip for sr-only text). |
variant | ButtonVariant | "ghost" | Forwarded to the underlying Button. |
size | ButtonSize | "icon-sm" | Forwarded to the underlying Button. |
<MessageBranch> (compound)
| Prop | Type | Default | Description |
|---|---|---|---|
defaultBranch | number | 0 | Initial branch index. |
onBranchChange | (index: number) => void | - | Fires when the user navigates between branches. |
Provides context for MessageBranchContent, MessageBranchSelector, MessageBranchPrevious, MessageBranchNext and MessageBranchPage.
Composition
<Message>sets alignment and exposes the role via group classes.<MessageContent>is the surface that adapts to user vs assistant.<MessageResponse>renders streaming markdown inside the content.<MessageAttachments>/<MessageAttachment>sit above the content for file previews.<MessageToolbar>+<MessageActions>+<MessageAction>appear under the content for copy, regenerate, like and other affordances.<MessageBranch>wraps several<MessageBranchContent>siblings so users can step through alternate completions.
Variations
Streaming assistant response
Use while tokens are still arriving. Pair with Reasoning to expose thinking.
<Message from="assistant">
<MessageContent>
<Reasoning isStreaming={status === "streaming"}>
<ReasoningTrigger />
<ReasoningContent>{thinking}</ReasoningContent>
</Reasoning>
<MessageResponse>{partialMarkdown}</MessageResponse>
</MessageContent>
</Message>User turn with attachments
User attachments float above the bubble. The attachment row aligns to the trailing edge to match the bubble's right alignment.
<Message from="user">
<MessageAttachments>
{files.map((file) => (
<MessageAttachment
key={file.url}
data={file}
onRemove={() => removeFile(file.url)}
/>
))}
</MessageAttachments>
<MessageContent>{prompt}</MessageContent>
</Message>Assistant turn with branch navigation and toolbar
Lets users walk through regenerated alternates and act on the current one.
<Message from="assistant">
<MessageBranch>
<MessageBranchContent>
<MessageContent>
<MessageResponse>{variantA}</MessageResponse>
</MessageContent>
</MessageBranchContent>
<MessageBranchContent>
<MessageContent>
<MessageResponse>{variantB}</MessageResponse>
</MessageContent>
</MessageBranchContent>
</MessageBranch>
<MessageToolbar>
<MessageBranchSelector from="assistant">
<MessageBranchPrevious />
<MessageBranchPage />
<MessageBranchNext />
</MessageBranchSelector>
<MessageActions>
<MessageAction tooltip="Copy" onClick={copy}>
<CopyIcon />
</MessageAction>
<MessageAction tooltip="Regenerate" onClick={regenerate}>
<RefreshIcon />
</MessageAction>
</MessageActions>
</MessageToolbar>
</Message>Accessibility
- Keyboard:
MessageActionand branch controls are realButtons, so they participate in normal Tab order and respond to Enter / Space. - ARIA:
MessageActionreadstooltip(orlabel) assr-onlytext, so icon-only actions still announce.MessageBranchPreviousandMessageBranchNextcarry explicitaria-labels ("Previous branch", "Next branch").MessageAttachmentremove buttons announce as "Remove attachment". - Screen readers: Branch controls disable themselves (and announce as disabled) whenever there is only one branch. The selector hides itself entirely below two branches.
- Focus management: Toolbar actions stay in document order so screen readers narrate the turn before its affordances.
Related
- Conversation - scroll-to-bottom container for a stream of messages
- Reasoning - collapsible thinking block to embed inside a Message
- Sources - cite the documents an assistant turn used
- InlineCitation - inline footnote-style citations inside
MessageResponse