gridland
Components

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.

Message
?

Run demo

Terminal
bunx @gridland/demo message
Terminal
curl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s message

Installation

Terminal
bunx create-gridland add message

Usage

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.

Message + PromptInput
?

Streaming

Pass isStreaming to indicate the message is still being generated.

Streaming message
<Message role="assistant" isStreaming>
  <MessageContent>
    <MessageText>{partialText}</MessageText>
  </MessageContent>
</Message>

Markdown

Use MessageMarkdown to render markdown content via the OpenTUI markdown intrinsic.

Markdown content
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.

Custom background
<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.

Vercel AI SDK
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.

ComponentDescription
MessageContentBubble wrapper with role-based background color
MessageTextText with word wrap
MessageMarkdownMarkdown content via OpenTUI intrinsic

useMessage

Access message context from within any sub-component.

Custom 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

PropTypeDefaultDescription
role"user" | "assistant" | "system"-Message role — determines alignment and background
isStreamingbooleanfalseWhether the message is currently streaming
backgroundColorstring-Override the default role-based background color
childrenReactNode-Sub-components

MessageContent

PropTypeDefaultDescription
childrenReactNode-Compound components to render inside the message bubble

MessageText

PropTypeDefaultDescription
childrenstring-Text content

MessageMarkdown

PropTypeDefaultDescription
childrenstring-Markdown string to render

Migrating from v0

The following compound sub-components were removed in this version:

RemovedReplacement
Message.ContentMessageContent (named export)
Message.TextMessageText (named export)
Message.ReasoningUse ChainOfThought component as a sibling
Message.ToolCallBuild your own with useMessage() for context
Message.SourceBuild your own with useMessage() for context
Message.FooterBuild your own with useMessage() for context

MessageContextValue

interface MessageContextValue {
  role: MessageRole
  isStreaming: boolean
  backgroundColor: string
  textColor: string
}