Chat Message
Display a conversation turn with actions such as copy, regenerate, rate and edit.
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use one for every turn in a conversation, inside a Chat Message List, which groups consecutive messages and announces them. An assistant message renders its content as streamed markdown across the column. A user message is a bubble on the inline-end side, and a system message is a centered notice between hairlines. Hover the answer in the preview to see its actions, or edit the question.
import { ChatMessage } from '@youj/react'
<ChatMessage role="user" name="Ada Lovelace" content="Why did checkout get slower?" onEdit={resend} />
<ChatMessage
role="assistant"
name="Youj"
content={reply.stream}
streaming={reply.streaming}
feedback={reply.feedback}
onFeedbackChange={rate}
onRegenerate={regenerate}
/>Names default to "Assistant", "You" and "System". timestamp takes a Date, a string or a number, and shows the time with the full date in its title. Messages are memoised, so pass stable callbacks and a message re-renders only when its own props change.
children render before the text, for what the assistant did first: a typing indicator or reasoning, tool calls, attachments. footer renders after it, for follow-ups. sources turns [1] in the text into a citation and adds a row of sources under the answer once it's done.
Description
An editable question and an assistant answer you can rate and regenerate.
A system notice and an assistant message with two actions of its own, Save and Share.
Copy is on by default (copyable={false} removes it). Regenerate shows on assistant messages when onRegenerate is set, the good and bad pair when feedback or onFeedbackChange is, and Edit on user messages when onEdit is. Editing swaps the bubble for a field: Enter saves, Escape cancels, and focus returns to Edit.
Actions sit in one toolbar that stays quiet until wanted: it shows on hover and focus, always on touch screens and on the latest message, and never while the message streams. Add your own after the built-in ones with actions and ChatMessageAction, which is an icon button with a tooltip, or a toggle when you pass pressed. A custom avatar replaces the default one.
An answer that drops part-way through, with the error and a retry that streams it whole.
error shows under whatever arrived, with Retry when onRetry is set, and is announced assertively. The partial answer stays, and its unfinished end renders as it did while streaming. For an answer the reader stopped, set incomplete instead. The preview drops its first try part-way. Retry streams it again.
Code
message-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsrole, name, avatar… (27)
Props
| Prop | Type | |
|---|---|---|
roleRequired | ChatRole'user''assistant''system' | – |
name | stringThe author, shown and announced. Defaults: "Assistant", "You", "System". | – |
avatar | ReactNodeA custom avatar, e.g. | – |
timestamp | Date | string | number | – |
content | ChatTextThe text. Markdown for assistant messages, plain text otherwise. A ChatTextSource streams it. | – |
streaming | booleanThe assistant is still writing it: the text streams in, and actions wait until it's done. | – |
children | ReactNodeBlocks before the text: reasoning, tool calls, attachments. | – |
footer | ReactNodeBlocks after the text: follow-ups, anything else. | – |
sources | ChatSource[]Where the answer came from: | – |
headingLevel | numberThe outline level for the answer's top heading. Default 2 (see ChatMarkdown). | – |
error | stringWhy the message failed. Shown under whatever arrived, with Retry when | – |
incomplete | booleanThe answer was cut off (stopped): its unfinished end renders as it did while streaming. Implied by | – |
onRetry | () => void | – |
onRegenerate | () => voidShows Regenerate on assistant messages. | – |
feedback | ChatFeedback'up''down'Good / bad response, controlled. Shows the pair when set or when | – |
onFeedbackChange | (feedback: ChatFeedback) => void | – |
onEdit | (text: string) => voidShows Edit on user messages and receives the new text. | – |
actions | ReactNodeMore actions after the built-in ones: ChatMessageAction elements. | – |
copyable | booleanOffer Copy. default true | true |
ref | Ref<HTMLElement>Allows getting a ref to the component instance.
Once the component unmounts, React will set | – |
Other props pass through: extends ComponentProps<'article'>. | ||
ChatMessageAction
| Prop | Type | |
|---|---|---|
labelRequired | stringThe action's name and tooltip. | – |
childrenRequired | ReactNode | – |
onClick | () => void | – |
pressed | booleanMakes it a toggle (aria-pressed), e.g. good response. | – |
onPressedChange | (pressed: boolean) => void | – |
disabled | boolean | – |
ref | Ref<HTMLButtonElement> | – |
MotionActions show and hide, writing, copied… · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Actions show and hidethe toolbar fades on hover and focus | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Writingthe assistant avatar’s sparkles twinkle while it streams | no token loop | loop | |
| Copiedthe back sheet tucks in behind the front one | duration.baseease.exit220ms · cubic-bezier(0.4, 0, 1, 1) | 220mscubic-bezier(0.4, 0, 1, 1) | |
Copied checkwritten on the page, after a base delay | duration.slow340ms | 340ms | |
| Copied holdbefore the icon and the tooltip return to rest | duration.deliberate560ms · × 3 | 560ms× 3 | |
| Good or bad responsethe thumb rocks −16°, 5°, 0° as it turns on | duration.deliberateease.emphasized560ms · cubic-bezier(0.6, 0, 0.1, 1) | 560mscubic-bezier(0.6, 0, 0.1, 1) | |
| Error markthe warning rocks once as it mounts | duration.deliberateease.standard560ms · cubic-bezier(0.2, 0, 0, 1) | 560mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
The toolbar shows and hides at once, the avatar holds still, and the icons change without playing.
The text's own motion (words fading in, the caret) belongs to Chat Markdown.
Reduced motion
The toolbar appears without fading, the assistant's avatar stops twinkling, and the copy, rating and warning icons change state without animating. Streamed text arrives in whole sentences (see Chat Markdown).
AccessibilityKeyboard, ARIA, focus
Accessibility
- Each message is an
articlenamed by its header, the author and the time, even when grouping hides the header visually. - While streaming it sets
aria-busy. Inside a list, a finished assistant answer is announced once, whole, and a failure assertively. - The actions are one toolbar named "Message actions": a single tab stop, with arrow keys moving inside it. Every action has a name and a tooltip, and the rating buttons are toggles with
aria-pressed. - The edit field is named "Edit message" and takes focus with the caret at the end. Escape cancels and returns focus to Edit.