Message
An SDK-agnostic chat message with role-based styling and streaming support
A chat message component with role-based styling and streaming support. Message
is a thin layout shell — it provides alignment, background color, and context.
Content goes inside MessageContent, text inside MessageText. Other concerns
(tool calls, sources, reasoning, footer) are separate components composed
alongside Message, not inside it.
Run demo
bunx @gridland/demo messagecurl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s messageInstallation
bunx create-gridland add messageUsage
import { Message, MessageContent, MessageText } from "@/components/ui/message"<Message role="user">
<MessageContent>
<MessageText>Hello, can you help me?</MessageText>
</MessageContent>
</Message>
<Message role="assistant">
<MessageContent>
<MessageText>Of course! What do you need?</MessageText>
</MessageContent>
</Message>Examples
With PromptInput
Combine Message with PromptInput for a complete conversation view.
Streaming
Pass isStreaming to indicate the message is still being generated.
<Message role="assistant" isStreaming>
<MessageContent>
<MessageText>{partialText}</MessageText>
</MessageContent>
</Message>Markdown
Use MessageMarkdown to render markdown content via the OpenTUI markdown
intrinsic.
import { MessageMarkdown } from "@/components/ui/message"
<Message role="assistant">
<MessageContent>
<MessageMarkdown>{"# Hello\n\nThis is **bold** text."}</MessageMarkdown>
</MessageContent>
</Message>Custom Background Color
Override the default role-based background with backgroundColor.
<Message role="assistant" backgroundColor="#1a2b3c">
<MessageContent>
<MessageText>Custom styled message.</MessageText>
</MessageContent>
</Message>Mapping Vercel AI SDK Parts
Map message.parts from useChat to sub-components. The consumer owns the
mapping — the component has no SDK dependency.
import { useChat } from "@ai-sdk/react"
import { Message, MessageContent, MessageText } from "@/components/ui/message"
const { messages, status } = useChat({ api: "/api/chat" })
{messages.map((msg, i) => {
const isLast = i === messages.length - 1
const msgStreaming = isLast && msg.role === "assistant" && status === "streaming"
return (
<Message key={msg.id} role={msg.role} isStreaming={msgStreaming}>
<MessageContent>
{msg.parts?.filter(p => p.type === "text").map((part, j) => (
<MessageText key={j}>
{part.text}
</MessageText>
))}
</MessageContent>
</Message>
)
})}Compound Components
All compound components read shared state (role, streaming, background color) from
Message via context. No prop drilling needed.
| Component | Description |
|---|---|
MessageContent | Bubble wrapper with role-based background color |
MessageText | Text with word wrap |
MessageMarkdown | Markdown content via OpenTUI intrinsic |
useMessage
Access message context from within any sub-component.
import { useMessage } from "@/components/ui/message"
function MyCustomStatus() {
const { role, isStreaming, textColor } = useMessage()
return <text>{role}: {isStreaming ? "streaming..." : "done"}</text>
}API Reference
Message
| Prop | Type | Default | Description |
|---|---|---|---|
role | "user" | "assistant" | "system" | - | Message role — determines alignment and background |
isStreaming | boolean | false | Whether the message is currently streaming |
backgroundColor | string | - | Override the default role-based background color |
children | ReactNode | - | Sub-components |
MessageContent
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Compound components to render inside the message bubble |
MessageText
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | - | Text content |
MessageMarkdown
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | - | Markdown string to render |
Migrating from v0
The following compound sub-components were removed in this version:
| Removed | Replacement |
|---|---|
Message.Content | MessageContent (named export) |
Message.Text | MessageText (named export) |
Message.Reasoning | Use ChainOfThought component as a sibling |
Message.ToolCall | Build your own with useMessage() for context |
Message.Source | Build your own with useMessage() for context |
Message.Footer | Build your own with useMessage() for context |
MessageContextValue
interface MessageContextValue {
role: MessageRole
isStreaming: boolean
backgroundColor: string
textColor: string
}