gridland
Components

Message

An SDK-agnostic chat message with text, reasoning, tool calls, and sources

A compound chat message component. Sub-components take flat props — no SDK-specific types required. Works with Vercel AI SDK, Anthropic SDK, OpenAI SDK, LangChain, or any other provider. The consumer maps their SDK's output to sub-components.

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

bunx shadcn@latest add @gridland/message

Usage

import { Message } from "@/components/ui/message"
<Message role="user">
  <Message.Content>
    <Message.Text>Hello, can you help me?</Message.Text>
  </Message.Content>
</Message>

<Message role="assistant">
  <Message.Content>
    <Message.Text>Of course! What do you need?</Message.Text>
  </Message.Content>
  <Message.Footer model="claude-opus-4-6" />
</Message>

Examples

With PromptInput

Combine Message with PromptInput for a complete conversation view.

Message + PromptInput
?

Streaming

Pass isStreaming to the message and isLast to the final text part to show a streaming cursor.

Streaming message
<Message role="assistant" isStreaming>
  <Message.Content>
    <Message.Text isLast>{partialText}</Message.Text>
  </Message.Content>
</Message>

Tool Calls

Render tool calls with the generic 4-state model. Map your SDK's tool states to pending, running, completed, or error.

Tool call states
<Message.ToolCall name="readFile" state="running" />
<Message.ToolCall name="search" state="completed" result="3 results found" />
<Message.ToolCall name="deploy" state="error" result="Permission denied" />

Reasoning

Render a collapsible reasoning/thinking block above the content bubble.

With reasoning
<Message role="assistant">
  <Message.Reasoning duration="1.2s" collapsed={false} />
  <Message.Content>
    <Message.Text>Here is the answer.</Message.Text>
  </Message.Content>
</Message>

Sources

Render numbered source citations.

Source citations
<Message.Source title="API Reference" url="https://docs.example.com" index={0} />
<Message.Source title="Tutorial" url="https://example.com/guide" index={1} />

Show model attribution and timestamp below the bubble.

With footer
<Message role="assistant">
  <Message.Content>
    <Message.Text>Here is the answer.</Message.Text>
  </Message.Content>
  <Message.Footer model="claude-opus-4-6" timestamp="just now" />
</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"

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}>
      <Message.Content>
        {msg.parts?.map((part, j) => {
          const isLastPart = j === msg.parts.length - 1
          switch (part.type) {
            case "text":
              return <Message.Text key={j} isLast={isLastPart && msgStreaming}>{part.text}</Message.Text>
            case "tool-invocation":
              return (
                <Message.ToolCall
                  key={j}
                  name={part.toolInvocation.toolName}
                  state={part.toolInvocation.state === "result" ? "completed" : "running"}
                  result={part.toolInvocation.result}
                />
              )
            case "source-url":
              return <Message.Source key={j} title={part.title} url={part.url} index={j} />
            default:
              return null
          }
        })}
      </Message.Content>
    </Message>
  )
})}

Mapping Anthropic SDK

Anthropic SDK
// Map Anthropic content blocks to sub-components
msg.content.map((block, j) => {
  switch (block.type) {
    case "text":
      return <Message.Text key={j}>{block.text}</Message.Text>
    case "thinking":
      return <Message.Reasoning key={j} collapsed={false}>{block.thinking}</Message.Reasoning>
    case "tool_use":
      return <Message.ToolCall key={j} name={block.name} state="running" />
  }
})

Compound Components

All sub-components read shared state (role, streaming, background color) from Message via context. No prop drilling needed.

ComponentDescription
Message.ContentBubble wrapper with role-based background color
Message.TextText with word wrap and optional streaming cursor
Message.ReasoningCollapsible Chain of Thought block
Message.ToolCallTool call with status icon and result
Message.SourceNumbered source citation
Message.FooterModel attribution and timestamp

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>
}

Tool Call States

The component uses 4 generic states that map to any SDK.

StateIconColorUse case
pendingmutedInput being prepared, waiting to execute
runningwarningTool is executing
completedsuccessTool finished with result
errorerrorTool execution failed

API Reference

Message

PropTypeDefaultDescription
role"user" | "assistant" | "system"-Message role — determines alignment and background
isStreamingbooleanfalseShow streaming cursor on the last text part
streamingCursorstring"▎"Cursor character shown while streaming
backgroundColorstring-Override the default role-based background color
childrenReactNode-Compound sub-components

Message.Text

PropTypeDefaultDescription
childrenstring-Text content
isLastbooleanfalseShow the streaming cursor after this part

Message.Reasoning

PropTypeDefaultDescription
durationstring-Duration label shown in the header
stepsStep[]-Structured thinking steps
collapsedbooleantrueWhether the block starts collapsed
childrenReactNode-Freeform content (used when steps are not provided)

Message.ToolCall

PropTypeDefaultDescription
namestring-Tool name
state"pending" | "running" | "completed" | "error""pending"Tool execution state
resultunknown-Tool result (shown when completed or error)
colorstring-Override the default state color

Message.Source

PropTypeDescription
titlestring?Source title
urlstring?Source URL
indexnumberZero-based index for the citation number

Message.Footer

PropTypeDescription
modelstring?Model name
timestampstring?Timestamp label

MessageContextValue

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

Part Types (optional helpers)

The component exports optional type definitions for consumers who want a standard intermediate format. Sub-components do NOT depend on these types.

type MessagePart = TextPart | ReasoningPart | ToolCallPart | SourcePart
TypeFields
TextParttype: "text", text: string
ReasoningParttype: "reasoning", text?, duration?, steps?, collapsed?
ToolCallParttype: "tool-call", name, state, args?, result?
SourceParttype: "source", title?, url?