Skip to content
pandacoderz/ui

Message

Chat message with streaming markdown, avatar, and an action bar.

Should I stream responses or wait for the full completion?
AI

Here's a quick comparison of the two approaches:

ApproachLatencyCost
StreamingLowSame
BatchedHighSame

Streaming keeps the UI responsive. A minimal handler looks like this:

ts
for await (const chunk of stream) {  setText((prev) => prev + chunk);}

Use setText with a functional update so out-of-order renders never drop a chunk.

Installation

npx shadcn@latest add https://ui.spencerwueste.com/r/message.json

MessageMarkdown renders with Streamdown, which handles incomplete markdown while tokens stream in. Only the Shiki code plugin is included by default; add @streamdown/math or @streamdown/mermaid to streamdownPlugins in message.tsx if you need them.

Usage

import {
  Message,
  MessageAction,
  MessageActionGroup,
  MessageActions,
  MessageAvatar,
  MessageContent,
  MessageMarkdown,
  MessageStack,
} from "@/components/pandacoderz-ui/message";
<Message from="assistant">
  <MessageAvatar fallback="AI" />
  <MessageStack>
    <MessageContent>
      <MessageMarkdown>{markdown}</MessageMarkdown>
    </MessageContent>
    <MessageActions>
      <MessageActionGroup>
        <MessageAction asChild tooltip="Copy">
          <Button variant="ghost" size="icon-sm">…</Button>
        </MessageAction>
      </MessageActionGroup>
    </MessageActions>
  </MessageStack>
</Message>

API Reference

Message

Prop Type Description
from "user" | "assistant" Required. Aligns the message and sets the bubble style for descendants.

MessageMarkdown

Accepts all Streamdown props. Pass isAnimating while streaming to enable Streamdown’s incomplete-block handling. Override components to customize any element; the defaults style code fences, inline code, and tables.

MessageAvatar

Prop Type Description
src string Image URL. Optional.
fallback ReactNode Rendered when no image or while loading.
size "sm" | "default" | "lg" Avatar size.

MessageAction

Prop Type Description
asChild boolean Merge into the child element.
tooltip string | { content, side, shortcut } Tooltip content and optional keyboard hint.