Message Composition Primitives
The custom renderer lets you take over how a message is drawn entirely. But most of the time what you want to change is the content, not the layout — full-width bot text, a right-aligned user bubble, the placement of quick replies and the message-action row should all stay as they are.
So the SDK exports the primitives its default rows are built from, ready to compose.
Message Composition Primitives
To customize a message row's content without re-implementing the SDK's layout (full-width bot text, right-aligned user bubble, timestamp format and theme colors), compose the exported primitives: TemplateBox, TemplateBoxContent, BotMessageText, UserMessageText and Time. Use them from renderMessageContent and fall back with renderDefaultContent() for rows you don't handle.
import {
TemplateBox, TemplateBoxContent,
BotMessageText, UserMessageText, Time,
} from '@asgard-js/react';
<Chatbot
renderMessageContent={({ message, renderDefaultContent }) => {
if (message.type === 'user') {
return (
<TemplateBox type="user" direction="horizontal">
<UserMessageText>{/* your content */}</UserMessageText>
<Time time={message.time} />
</TemplateBox>
);
}
return renderDefaultContent();
}}
/>Both modes should look identical in layout — the only difference is that the composed mode turns the leading mention into a chip. That is the point of these primitives: change the content, keep the layout.
The Primitives
| Primitive | Role |
|---|---|
TemplateBox | The row frame — alignment and direction (type: 'user' | 'bot') |
TemplateBoxContent | The bot row's content container; handles quick replies and message actions |
BotMessageText | The bot text body, with Markdown rendering and theme colors |
UserMessageText | The user text bubble |
Time | Deprecated — renders nothing (see below) |
Usage
Use them from renderMessageContent, and fall back with renderDefaultContent()
for rows you don't handle:
import {
TemplateBox,
TemplateBoxContent,
BotMessageText,
UserMessageText,
} from "@asgard-js/react";
import { MessageTemplateType } from "@asgard-js/core";
<Chatbot
renderMessageContent={({ message, renderDefaultContent }) => {
// User row: turn the leading mention into a chip, keep everything else
if (message.type === "user" && message.text.startsWith(MENTION)) {
return (
<TemplateBox type="user" direction="horizontal">
<UserMessageText>
<span className="mention-chip">{MENTION}</span>
{message.text.slice(MENTION.length)}
</UserMessageText>
</TemplateBox>
);
}
// Bot text row
if (message.type === "bot") {
const template = message.message?.template;
const isText = template?.type === MessageTemplateType.TEXT || !template?.type;
const text = message.isTyping ? message.typingText : message.message?.text;
if (isText && text) {
return (
<TemplateBox type="bot" direction="horizontal">
<TemplateBoxContent
message={message}
quickReplies={template?.quickReplies}
references={template?.references}
>
<BotMessageText>{text}</BotMessageText>
</TemplateBoxContent>
</TemplateBox>
);
}
}
return renderDefaultContent();
}}
/>
Text While Streaming
When message.isTyping is true, read message.typingText rather than
message.message.text — the latter is only populated once the message completes. The
example above already handles this.
Why Not Just Hand-roll It
Hand-rolled rows drift from these, and keep drifting every time the SDK changes:
- Bot text is full-width with no bubble; only user rows are bubbles — different layout rules
TemplateBoxContentalso positions quick replies and the message-action row- Alignment, direction and theme colors all hang off
TemplateBoxrather than being scattered
In short: change the content, keep the layout.
Time No Longer Renders Anything
The chat surface no longer shows message timestamps anywhere. <Time /> is kept as a no-op
and every TimeProps field is ignored, purely so existing callers keep compiling; delete your
<Time /> when convenient.
The value it used to render was never the message's own time: the SDK stamped new Date()
as each frame arrived, and a GET rejoin replays the whole history at once, so every replayed
message was re-stamped with the moment the page opened. The backend carries no per-message
timestamp to put in its place.
The time field on ConversationMessage is deprecated for the same reason — nothing in the
SDK renders it.
ChatHeader
The same idea applies to the heading bar. ChatHeader is exported for use with
renderHeader; to swap only the title area while keeping the rest of the structure,
use renderTitle. See Custom Header.
See Also
- Custom Renderer — take over a message entirely
- Message Actions — the action row under a message
- Custom Header — the heading bar's three customization tiers