React UI
Message rendering
Render default message parts first, then customize Markdown, attachments, tool cards, and actions.
This recipe keeps default rendering for most parts and customizes only the parts that usually need product UI: Markdown text, attachments, and tool cards.
If you only need user/assistant alignment and ordinary text rendering, stay with
<Thread.Messages /> and style the emitted data-role attributes first.
Full renderer
import { Message } from "@anvia/react-ui";
export function AgentMessage() {
return (
<Message.Root className="message">
<Message.Content className="message-content">
<Message.Parts>
{(part) => {
if (part.type === "text") {
return (
<Message.Part className="text-part">
<Message.Markdown
components={{
code(props) {
return <Message.CodeBlock {...props} />;
},
}}
/>
</Message.Part>
);
}
if (part.type === "attachment") {
return (
<Message.Part className="attachment-part">
<Message.Attachment className="attachment-card" />
</Message.Part>
);
}
if (part.type === "tool") {
return (
<Message.Part className="tool-part">
<Message.Tool className="tool-card" renderWhen="always" />
</Message.Part>
);
}
return <Message.Part className="message-part" />;
}}
</Message.Parts>
</Message.Content>
<Message.Actions className="message-actions">
<Message.Copy>Copy</Message.Copy>
<Message.Regenerate>Retry</Message.Regenerate>
</Message.Actions>
</Message.Root>
);
}.message {
display: grid;
gap: 8px;
}
.message[data-role="user"] {
justify-items: end;
}
.message-content {
max-width: min(720px, 100%);
}
.message[data-role="user"] .message-content {
max-width: min(620px, 88%);
border-radius: 18px;
background: #27272a;
padding: 10px 14px;
}
.text-part {
line-height: 1.7;
}
.attachment-card,
.tool-card {
width: min(560px, 100%);
border: 1px solid #3f3f46;
border-radius: 10px;
background: #18181b;
padding: 12px;
}
.message-actions {
display: flex;
gap: 6px;
color: #a1a1aa;
}import { Message } from "@anvia/react-ui";
export function AgentMessage() {
return (
<Message.Root className="group/message grid gap-2 data-[role=user]:justify-items-end">
<Message.Content
className="
max-w-[min(720px,100%)]
group-data-[role=user]/message:max-w-[min(620px,88%)]
group-data-[role=user]/message:rounded-[18px]
group-data-[role=user]/message:bg-zinc-800
group-data-[role=user]/message:px-3.5
group-data-[role=user]/message:py-2.5
"
>
<Message.Parts>
{(part) => {
if (part.type === "text") {
return (
<Message.Part className="max-w-none leading-7">
<Message.Markdown />
</Message.Part>
);
}
if (part.type === "attachment") {
return (
<Message.Part>
<Message.Attachment className="w-full max-w-xl rounded-lg border border-zinc-700 bg-zinc-900 p-3" />
</Message.Part>
);
}
if (part.type === "tool") {
return (
<Message.Part>
<Message.Tool className="grid w-full max-w-xl gap-2 rounded-lg border border-zinc-700 bg-zinc-900 p-3" />
</Message.Part>
);
}
return <Message.Part />;
}}
</Message.Parts>
</Message.Content>
<Message.Actions className="flex gap-1.5 text-sm text-zinc-400">
<Message.Copy>Copy</Message.Copy>
<Message.Regenerate>Retry</Message.Regenerate>
</Message.Actions>
</Message.Root>
);
}Role-aware layout
Message.Root emits data-role, so the same component can style user and assistant messages
differently. Keep the user bubble small and let assistant content use the main reading width.
Copy and regenerate controls
Actions can use regular buttons or design-system components with asChild.
<Message.Actions className="message-actions">
<Message.Copy asChild>
<button type="button">Copy answer</button>
</Message.Copy>
<Message.Regenerate asChild>
<button type="button">Regenerate</button>
</Message.Regenerate>
</Message.Actions>
When to branch
Branch inside Message.Parts only for part types that need a different experience. For ordinary
data, reasoning, and error parts, keep <Message.Part /> as the fallback so the default renderer
continues to work.
