Message
Chat message row pairing an avatar with a content column; mirrors for the current author.
Examples
Thread
Compose MessageGroup with alternating Message rows. The avatar, header, and footer are optional per row.
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Bubble, BubbleContent, BubbleReactions } from "@codefast/ui/bubble";
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageGroup,
MessageHeader,
} from "@codefast/ui/message";
export function MessageThread() {
return (
<MessageGroup className="w-full max-w-sm">
<Message>
<MessageAvatar>
<Avatar size="sm">
<AvatarImage
alt="@ada"
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128&h=128&fit=crop&q=80"
/>
<AvatarFallback>AD</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<MessageHeader>Ada Lovelace</MessageHeader>
<Bubble variant="muted">
<BubbleContent>Morning! The new registry docs are live — want to review before we ship?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble align="end">
<BubbleContent>Yes! Joining now.</BubbleContent>
<BubbleReactions aria-label="Reaction: thumbs up" role="img">
👍
</BubbleReactions>
</Bubble>
<MessageFooter>9:41 AM · Read</MessageFooter>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar size="sm">
<AvatarImage
alt="@ada"
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128&h=128&fit=crop&q=80"
/>
<AvatarFallback>AD</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>Great — starting with the chat components.</BubbleContent>
</Bubble>
<MessageFooter>9:42 AM</MessageFooter>
</MessageContent>
</Message>
</MessageGroup>
);
}
Avatar
Attach an author avatar with MessageAvatar; consecutive same-author rows can drop it.
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Bubble, BubbleContent, BubbleGroup } from "@codefast/ui/bubble";
import { Message, MessageAvatar, MessageContent } from "@codefast/ui/message";
export function MessageAvatarDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
alt="@rocky"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=128&h=128&fit=crop&q=80"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>The build failed during dependency installation.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage
alt="@grace"
src="https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=128&h=128&fit=crop&q=80"
/>
<AvatarFallback>G</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>Can you share the exact error?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
alt="@rocky"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=128&h=128&fit=crop&q=80"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<BubbleGroup>
<Bubble variant="muted">
<BubbleContent>Here’s the error from the logs</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
Something went wrong with the build. The libraries are not installed correctly. Try running the build
again.
</BubbleContent>
</Bubble>
</BubbleGroup>
</MessageContent>
</Message>
</div>
);
}
Group
MessageGroup stacks related rows; an empty MessageAvatar keeps content aligned without a picture.
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Bubble, BubbleContent } from "@codefast/ui/bubble";
import { Message, MessageAvatar, MessageContent, MessageGroup } from "@codefast/ui/message";
export function MessageGroupDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<MessageGroup>
<Message>
<MessageAvatar />
<MessageContent>
<Bubble variant="muted">
<BubbleContent>I checked the registry addresses.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
alt="@rocky"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=128&h=128&fit=crop&q=80"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>The component and example JSON now live under the UI registry.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
</div>
);
}
Actions
Put per-message controls (copy, react, retry) in MessageFooter; it aligns with the row.
import { Bubble, BubbleContent } from "@codefast/ui/bubble";
import { Button } from "@codefast/ui/button";
import { Message, MessageContent, MessageFooter } from "@codefast/ui/message";
import { CopyIcon, RefreshCcwIcon, ThumbsDownIcon, ThumbsUpIcon } from "lucide-react";
export function MessageActions() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>The install failure is coming from the workspace package.</BubbleContent>
</Bubble>
<MessageFooter>
<Button aria-label="Copy" size="icon-xs" title="Copy" variant="ghost">
<CopyIcon />
</Button>
<Button aria-label="Like" size="icon-xs" title="Like" variant="ghost">
<ThumbsUpIcon />
</Button>
<Button aria-label="Dislike" size="icon-xs" title="Dislike" variant="ghost">
<ThumbsDownIcon />
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble align="end">
<BubbleContent>Okay drop me a link. Taking a look…</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span className="font-normal text-destructive">Failed to send</span>
<Button aria-label="Retry" size="icon-xs" title="Retry" variant="ghost">
<RefreshCcwIcon />
</Button>
</MessageFooter>
</MessageContent>
</Message>
</div>
);
}
With attachments
Compose Attachment inside MessageContent to send and receive files alongside bubbles.
import {
Attachment,
AttachmentAction,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentMedia,
AttachmentTitle,
} from "@codefast/ui/attachment";
import { Bubble, BubbleContent } from "@codefast/ui/bubble";
import { Message, MessageContent } from "@codefast/ui/message";
import { Image } from "@unpic/react";
import { DownloadIcon, FileTextIcon } from "lucide-react";
export function MessageAttachment() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<Message align="end">
<MessageContent>
<Attachment orientation="vertical">
<AttachmentMedia variant="image">
<Image
alt="Workspace"
className="size-full object-cover"
layout="fullWidth"
src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=600&auto=format&fit=crop&q=80"
/>
</AttachmentMedia>
</Attachment>
<Bubble>
<BubbleContent>Here’s the image. Can you add it to the PDF? Use it for the cover page.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>Done. Here’s the PDF with the image added as the cover page.</BubbleContent>
</Bubble>
<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Download" size="icon-sm" title="Download" type="button" variant="secondary">
<DownloadIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Thanks. Looks good.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</div>
);
}
Markdown
Render assistant text in a ghost bubble as plain paragraphs; keep user messages as plain text.
import { Bubble, BubbleContent } from "@codefast/ui/bubble";
import { Message, MessageContent } from "@codefast/ui/message";
const response = `Here's how to render markdown in a message:
1. Render assistant text through Markdown.
2. Keep user messages as plain text.
3. Use a ghost bubble so the response is unframed.`;
export function MessageMarkdown() {
const paragraphs = response.split("\n\n");
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>How do I render markdown in a message?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="ghost">
<BubbleContent>
{paragraphs.map((paragraph) => (
<p key={paragraph} className="whitespace-pre-wrap">
{paragraph}
</p>
))}
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</div>
);
}
Usage
The minimal import and composition — see Examples below for styled, real-world variants.
import { Bubble, BubbleContent } from "@codefast/ui/bubble";
import { Message, MessageContent } from "@codefast/ui/message";
export function MessageUsage() {
return (
<Message>
<MessageContent>
<Bubble>
<BubbleContent>Hey, how's it going?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- align="end" reverses the row (avatar/content order) for the current user's own messages — no manual flex-direction needed.
- MessageAvatar lifts above MessageFooter automatically when a footer is present, so it doesn't collide with delivery-status text.
- Designed to sit inside MessageScroller for long, keyboard- and screen-reader-navigable transcripts.
API reference
Props for each part of the component. All native element props are also forwarded.
Message
A single message row: avatar plus content column.
align"start" | "end"Which author side the row belongs to; 'end' reverses the row direction.
Default
"start"
MessageContent
Column holding the bubbles and metadata; end-aligned rows self-align their children.
childrenReactNodeBubbles, MessageHeader, MessageFooter.
MessageAvatar
Optional author avatar; lifts above MessageFooter when a footer is present.
childrenReactNodeAn Avatar element.
MessageHeader
Metadata row above the bubbles, e.g. author name and timestamp.
childrenReactNodeHeader text such as name and time.
MessageFooter
Metadata row below the bubbles, e.g. delivery status and actions; aligns with the row.
childrenReactNodeFooter text or controls such as status and reaction buttons.
MessageGroup
Vertical stack of related messages.
childrenReactNodeOne or more Message rows.
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
- Message is presentational; put author and time in MessageHeader/MessageFooter as real text, not color or side alone.
- Row direction and spacing mirror automatically under a DirectionProvider (RTL).
- For long feeds, render messages inside MessageScroller so keyboard and screen-reader users can navigate the transcript.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Use align='end' for the current user's messages and 'start' for others.
- Attach avatars to incoming messages and drop them on consecutive same-author rows.
Don’t
- Don’t convey the author with alignment alone — keep a visible name or avatar.
- Don’t hard-code left/right; use align so the row mirrors in RTL.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.