Skip to main content

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 timestamp's format and theme color 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.

Loading chatbot...

The Primitives

PrimitiveRole
TemplateBoxThe row frame — alignment and direction (type: 'user' | 'bot')
TemplateBoxContentThe bot row's content container; handles quick replies and message actions
BotMessageTextThe bot text body, with Markdown rendering and theme colors
UserMessageTextThe user text bubble
TimeThe timestamp, formatted and themed via template.time.style

Usage

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";
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>
<Time time={message.time} />
</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
  • The timestamp's format and color come from the theme, not from hardcoded values
  • TemplateBoxContent also positions quick replies and the message-action row

In short: change the content, keep the layout.

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