Message

Building blocks for one chat transcript row: Message, MessageGroup, MessageAvatar, header, content and footer slots, a live marker and a typing indicator. A freshly sent row can pop up once from its trailing corner.

Preview

Message preview width

Install

Add it with the shadcn CLI, or copy the source manually.

$ bunx --bun shadcn add @karaui/message
@karaui not found? Add it to components.json
{
  "registries": {
    "@karaui": "https://karaui.kamisama-v0.my.id/r/{name}.json"
  }
}

@karaui is in shadcn's public registry directory, so the latest CLI needs no setup. On an older CLI, merge this into the existing file once and every @karaui/… install resolves.

API Reference

Message

from
"user" | "assistant"
—
animateIn?
boolean

Plays a trailing-edge pop-up once when this message row mounts.

false
className?
string
—

MessageGroup

spacing?
"default" | "compact"
compact
className?
string
—

MessageAvatar

placeholder?
boolean

Keep an empty avatar slot so grouped messages remain aligned.

false
className?
string
—

MessageContent

className?
string
—

MessageHeader

className?
string
—

MessageFooter

className?
string
—

MessageMarker

className?
string
—

MessageTyping

label?
string
Responding
className?
string
—

MessageBubble

variant?
"ghost" | "outline" | "solid" | "danger" | "soft" | "tint"
soft
align?
"end" | "start"

Defaults to the surrounding Message alignment when omitted.

—
animateIn?
boolean

Plays the bubble entrance once when this component mounts.

false
className?
string
—

MessageBubbleCollapsible

open?
boolean
—
defaultOpen?
boolean
false
onOpenChange?
((open: boolean) => void)
—
collapsedLines?
2 | 3 | 4 | 5 | 6
4
moreLabel?
ReactNode
Show more
lessLabel?
ReactNode
Show less
contentClassName?
string
—
triggerClassName?
string
—
className?
string
—

MessageBubbleContent

render?
ReactElement<unknown, string | JSXElementConstructor<any>>

Replaces the content element while preserving bubble styling.

—
className?
string
—

MessageBubbleGroup

spacing?
"default" | "compact"
compact
className?
string
—

MessageScroller

followOutput?
boolean

Keep streamed output pinned while the reader remains near the end.

true
followThreshold?
number

Distance from the end that still counts as following the output.

56
smooth?
boolean

Smoothly follow growing content.

true
onFollowChange?
((following: boolean) => void)

Reports when the reader leaves or returns to the live edge.

—
label?
string

Accessible label for the scrollable transcript.

Conversation
busy?
boolean

Marks the transcript as waiting for more streamed content.

—
navigation?
"rail"

Adds a compact rail for navigating between rendered Message rows.

—
navigationLabel?
string

Accessible label for the optional message navigation rail.

Message navigation
viewportClassName?
string
—
contentClassName?
string
—
railClassName?
string
—
viewportRef?
Ref<HTMLElement>
—
viewportProps?
Omit<DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>, "ref" | "children" | "className">
—
contentProps?
Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref" | "children" | "className">
—
className?
string
—

Composition

Assemble a row from separate pieces for who spoke, what was said and the details around it.

MessageGroup
└── Message
    ├── MessageAvatar
    └── MessageContent
        ├── MessageHeader
        ├── MessageBubble
        └── MessageFooter

Note: Message Bubble paints the content slot with a tone and shape. Message Scroller hosts the rows in a transcript that keeps up with new output. Agent Activity fills a row with live reasoning, searches and tool calls.

How it works

Each Message is one turn in the transcript. It lines up the avatar, the header and footer details and the content slot, and it does not care whether that slot holds a bubble, a tool result, an activity log or a full answer.

Updated