Message
betaChat message (user/assistant) with avatar, hover actions, ‹ 2/3 › branches, error/streaming states and an autoscrolling MessageList thread.
Two turns
minimal
Avatars, actions and versions
Name, time, avatar, copy/regenerate/edit, controlled thumbs, ‹ 2/3 › branches. Regenerate simulates an error with Retry.
full
Streaming and error
A streaming answer with reserved actions, and a failed one.
state
Autoscrolling thread
MessageList with StreamingText, CodeBlock and a shimmering “Thinking…” turn. Scroll up mid-stream to see “Jump to latest”.
composition
Scroll up while it streams.
Installation
npx shadcn@latest add https://kuro.abstergo.fyi/r/message.jsonCopies the source into components/kura/message with its dependencies (kura-theme, button, icon-button, use-controllable-state, use-reduced-motion, utils). Tokens come from the kura-theme item.
When to use it
Use it for
- Render the turns of a chat with an LLM: user bubbles on the right, assistant answers as open text on the left.
- Add per-message actions: copy, regenerate, edit, thumbs up/down and navigation between regenerated versions.
- Show a failed generation with an inline error and a Retry button.
- Wrap a whole conversation in MessageList for stick-to-bottom autoscroll with a “Jump to latest” pill.
Reach for something else when
- Only the streaming answer text, without chrome — use streaming-text.
- A model's hidden chain of thought — put a reasoning block inside the assistant Message.
- A tool invocation or an agent plan — use tool-call or agent-timeline inside the assistant Message.
- The input box at the bottom of a chat — use composer.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| from* | "user" | "assistant" | — | Who wrote it. user = right-aligned gray bubble with a tucked bottom-right corner; assistant = full-width open text. |
| name | React.ReactNode | — | Display name in the header, e.g. "Kura" or "You". |
| avatar | React.ReactNode | — | Avatar element, usually `<MessageAvatar name src? fallback? />`. |
| timestamp | Date | number | string | — | Sent time. A Date/epoch renders as a localized time (“10:42 AM”) in a <time> element with the full date as title; a string renders as-is. |
| status | "idle" | "streaming" | "error" | "idle" | streaming: sets aria-busy and keeps the action row invisible + inert (still laid out — no shift when it appears). error: dims the content and shows `error` with an optional Retry. |
| error | React.ReactNode | "Something went wrong." | Error text for status="error". |
| onRetry | () => void | — | Adds a Retry button to the error row. |
| actions | React.ReactNode | — | Action bar under the content, usually `<MessageActions>` with MessageCopyButton, MessageAction, MessageFeedback, MessageBranch. |
All native attributes of the root element, className and ref pass through.
Anatomy
- MessageListThread container (`role="log"` viewport). Sticks to the bottom while content grows, releases when the reader scrolls up, shows a “Jump to latest” pill. `data-at-bottom`.
data-slot="message-list" - MessageRoot. `data-from`, `data-status`, `aria-busy` while streaming.
data-slot="message" - HeaderName and time.
data-slot="message-header" - BodyContent slot: a flex column with gap-3, with markdown-friendly styles for p, ul/ol, a, strong, inline code, blockquote and h1–h3. Drop in rendered markdown, StreamingText or CodeBlocks.
data-slot="message-body" - Errorrole="alert" row for status="error".
data-slot="message-error" - MessageAvatar28px round avatar: image → custom fallback → initials.
data-slot="message-avatar" - MessageActionsrole="group" action bar; fades in on hover/focus-within, always visible on coarse pointers.
data-slot="message-actions" - MessageActionSmall labelled IconButton (tooltip included) for regenerate, edit, share…
data-slot="message-action" - MessageCopyButtonCopy action; the icon cross-fades to a check for 1.6s and a polite status announces it.
- MessageFeedbackThumbs up/down toggles (aria-pressed); pressing the active one clears it. `data-value`.
data-slot="message-feedback" - MessageBranch“‹ 2/3 ›” version switcher for regenerations/edits; renders nothing when total ≤ 1.
data-slot="message-branch" - Jump“Jump to latest” pill; `data-state="visible" | "hidden"` (inert when hidden).
data-slot="message-list-jump"
Accessibility
- Role:
log - MessageList's viewport is `role="log"` (implicitly polite) labelled “Conversation”, and focusable so the thread scrolls with the keyboard. A streaming Message sets `aria-busy`, so assistive tech announces the finished answer rather than each token.
- Every action is a labelled icon button with a tooltip; feedback toggles use aria-pressed; the branch switcher announces “Version 2 of 3”.
- Actions are revealed on hover AND keyboard focus (focus-within), and are always visible on touch devices.
- The error row is `role="alert"`.
- The hidden jump pill is inert and aria-hidden, so it never traps focus.
- Reduced motion: Jump-to-latest scrolls instantly and its pill only fades (no slide/scale); the copy check and thumbs swap without scale/blur.
| Keys | Action |
|---|---|
| Tab | Moves into a message's actions (revealing the bar) and through them |
| ↑ / ↓ / Page Up / Page Down | Scroll the focused thread |
| Enter / Space | Activate an action; toggle feedback |
Notes for agents
- Give MessageList a height (h-[480px], flex-1 inside a flex column, …) — it scrolls internally.
- Use `from`, not `role`, for the author (role is the ARIA attribute).
- Set status="streaming" on the assistant Message while StreamingText is running, then "idle" in its onComplete; pass `actions` from the start so the row is reserved.
- Message renders children inside a flex column with gap-3 — pass block elements (p, CodeBlock, lists) directly; do not add margins.
- For markdown, render it to elements (react-markdown etc.) inside Message; inline code, links, lists and blockquotes are styled by the body.
- Keep MessageBranch state (current version) in your store alongside the alternative texts.