Skip to content
Kura UI

Message

beta

Chat message (user/assistant) with avatar, hover actions, ‹ 2/3 › branches, error/streaming states and an autoscrolling MessageList thread.

@kura-ui/react/messageCopy page for agents.mdregistry.json

Two turns

minimal
How do I make a like button feel instant?
Render the new count optimistically and reconcile when the request finishes.

Avatars, actions and versions

Name, time, avatar, copy/regenerate/edit, controlled thumbs, ‹ 2/3 › branches. Regenerate simulates an error with Retry.

full
Ada Lovelace
You10:41
Why does my like button lag?
Kura
Kura10:42

Wrap the mutation in a transition and render from useOptimistic. If the action throws, React drops the optimistic value and the real count comes back on its own.

Version 2 of 3

Streaming and error

A streaming answer with reserved actions, and a failed one.

state
Kura
Kura
Kura
Kura
I started profiling the

Autoscrolling thread

MessageList with StreamingText, CodeBlock and a shimmering “Thinking…” turn. Scroll up mid-stream to see “Jump to latest”.

composition
Can you review the chat layout?
Kura
Sure. The structure is solid; two details stand out. The composer jumps when the keyboard opens on iOS, and new messages push the reader down even when they are reading older ones.
Show me the fix for the composer.
Kura
Pin it with dynamic viewport units:
composer.csscss
.composer { position: sticky; bottom: 0; max-height: 40dvh;}
Scroll up while it streams.

Installation

npx shadcn@latest add https://kuro.abstergo.fyi/r/message.json

Copies 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

PropTypeDefaultDescription
from*"user" | "assistant"—Who wrote it. user = right-aligned gray bubble with a tucked bottom-right corner; assistant = full-width open text.
nameReact.ReactNode—Display name in the header, e.g. "Kura" or "You".
avatarReact.ReactNode—Avatar element, usually `<MessageAvatar name src? fallback? />`.
timestampDate | 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.
errorReact.ReactNode"Something went wrong."Error text for status="error".
onRetry() => void—Adds a Retry button to the error row.
actionsReact.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

  1. 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"
  2. MessageRoot. `data-from`, `data-status`, `aria-busy` while streaming.data-slot="message"
  3. HeaderName and time.data-slot="message-header"
  4. 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"
  5. Errorrole="alert" row for status="error".data-slot="message-error"
  6. MessageAvatar28px round avatar: image → custom fallback → initials.data-slot="message-avatar"
  7. MessageActionsrole="group" action bar; fades in on hover/focus-within, always visible on coarse pointers.data-slot="message-actions"
  8. MessageActionSmall labelled IconButton (tooltip included) for regenerate, edit, share…data-slot="message-action"
  9. MessageCopyButtonCopy action; the icon cross-fades to a check for 1.6s and a polite status announces it.
  10. MessageFeedbackThumbs up/down toggles (aria-pressed); pressing the active one clears it. `data-value`.data-slot="message-feedback"
  11. MessageBranch“‹ 2/3 ›” version switcher for regenerations/edits; renders nothing when total ≤ 1.data-slot="message-branch"
  12. 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.
KeysAction
TabMoves into a message's actions (revealing the bar) and through them
↑ / ↓ / Page Up / Page DownScroll the focused thread
Enter / SpaceActivate 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.