Message
A role-aware chat message rendering string or ContentPart[] — markdown, image thumbnails with fullscreen, and file download chips — with a Checkpoint savepoint marker.
Message
The Message primitive is the role-aware chat row. It exposes data-role for theming, renders content as a markdown string or ContentPart[] (matching @localmode/react's message model), and supports contained vs. flat variants. Image parts render as thumbnails with a fullscreen dialog; file parts render a download chip — both from local bytes. MessageAvatar renders the role avatar, and Checkpoint is an inter-message savepoint marker enabling rollback/restore.
Preview
Installation
pnpm dlx shadcn@latest add @localmode/ui/conversation/messagenpx shadcn@latest add @localmode/ui/conversation/messageyarn dlx shadcn@latest add @localmode/ui/conversation/messagebunx --bun shadcn@latest add @localmode/ui/conversation/messageData source & dependencies
Data source: renders a markdown string or ContentPart[] you pass — works with any backend. Recommended producer: useChat from @localmode/react (on-device). See Use with the Vercel AI SDK for a cloud mapping.
@localmode/ui/lib/browser-utils—formatBytesfor file-size chips (installed automatically as a registry dependency)clsx+tailwind-merge— via the sharedcn()util (installed automatically as a registry dependency)
Files installed
message.tsx—Message,MessageContent,MessageAvatar,Checkpointlib/markdown.tsx— the streaming-safe markdown rendererlib/browser-utils.ts— generic browser helpers (formatBytesfor file-size chips)lib/utils.ts— thecn()helper (if not already present)
Props
MessageContent
Prop
Type
Examples
Render a message from useChat
import { Message, MessageAvatar, MessageContent } from '@/components/message';
export function Bubble({ message }) {
return (
<Message role={message.role}>
<MessageAvatar role={message.role} />
<MessageContent role={message.role} content={message.content} />
</Message>
);
}Checkpoint / restore
import { Checkpoint } from '@/components/message';
<Checkpoint label="Saved" onRestore={() => rollbackThreadTo(checkpointId)} />Customization
Swap the contained/flat bubble classes, change the avatar fallbacks, or extend MessagePart to add new part types (the component already handles text, image, and file). The markdown renderer in lib/markdown.tsx is dependency-free; replace it with streamdown or react-markdown + remark-gfm if you want richer rendering.
These primitives are presentational and hook-driven: they render props and emit callbacks, holding only local view state. The orchestration state (e.g. useChat) lives in your app. Every surface uses shadcn/ui CSS-variable utilities, so it inherits your theme — restyle the copied file freely.
Conversation
A scrollable chat surface with first-class scroll-anchoring — auto-pins while streaming, releases on scroll-up, and re-pins on return.
Response
A streaming markdown renderer with a cursor that tolerates partial markdown, plus optional typewriter reveal and swappable KaTeX/Mermaid renderers.