Message Parts
What an agent's answer is made of beyond its text — steps, a plan, status, changes, sources, the disclaimer, actions, versions and a fold.
- Status
- Since
0.41.0- Accessibility pattern
- states in words; actions stay in the Tab order
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Message Parts before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify message-partsInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/message-partsRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button, @vegastack/collapsible, @vegastack/disclosure-trigger, @vegastack/separator, @vegastack/spinner, @vegastack/status-line, @vegastack/tool-call-chip, @vegastack/use-overflow.
Usage
import { Citation } from "@/components/ui/cited-list";
import { Message, MessageContent } from "@/components/ui/message";
import {
MessageActions,
MessageDisclaimer,
MessageSources,
MessageStep,
MessageSteps,
} from "@/components/ui/message-parts";
<Message>
<MessageContent>
<MessageSteps summary="2 steps · Searched meetings">
<MessageStep label="Searched meetings" meta="2 results" />
<MessageStep label="Read Range review 28 Oct" />
</MessageSteps>
<p>You agreed to keep 4000 K for the IP65 range.</p>
<MessageSources>
<Citation href="/meetings/28">Range review 28 Oct</Citation>
</MessageSources>
<MessageDisclaimer>Generated by AI · can make mistakes</MessageDisclaimer>
<MessageActions>{/* Copy, Retry */}</MessageActions>
</MessageContent>
</Message>;Compose these inside MessageContent, next to the text. A person's
message needs none of them: it is Bubble with align="end".
MessageSources takes Citation chips, which it does not bring with
it: install cited-list too. An item the reader can't open is a Citation with render={<span />}.
The whole conversation
MessageScroller role="log" aria-label="Messages"
├── Message role="article", named "{author}, {time}"
│ └── MessageContent
│ ├── MessageStatus
│ ├── the text aria-busy while it streams
│ └── the other parts
└── one useAnnouncer region, mounted for the conversation's lifeKeep aria-busy on the text's own wrapper, never on Message: anything inside a busy subtree can go
unannounced, and the status and any failure sit next to the text. Announce the status and "replied"
through the one useAnnouncer region.
Anatomy
Examples
Status while it works
MessageStatus is the one line saying what the agent is doing now. Replace its text as the agent
moves on and remove it when the answer's text starts. The text below it is aria-busy until the
turn ends, so a screen reader doesn't read it word by word.
Steps
Closed by default, so the answer stays the first thing to read. A failed step keeps its reason in muted text under the chip, and a step that was running when the turn was stopped says so.
- Done:
- Failed:The site didn't answer.
- Running:
- Stopped:
Plan
A checklist the agent edits in place. Pass the whole list on every update.
- Done:Read the BOQ sheet
- Done:Match lines 1–9
- In progress:Match lines 10–20
- Failed:Fetch the project rates
- Stopped:Check the totals
- To do:Update the rows
Changes and hand-offs
One MessageChange per thing the agent changed, with Undo at the end of the row. Past a few, fold
them into one row that opens the full list. MessageHandoff marks where another agent takes over.
Notices and failures
MessageNotice is a quiet line with one action. A failed turn is MessageStatus with
status="error" and a Retry. Neither is a live region, so reopening a chat stays silent; pass live
to the one that just appeared because of what the person did.
Versions
Shows once an answer has been retried, so the earlier one stays within reach. It renders nothing
for a single version. formatPosition can say more: Previous answer (1 of 2).
Long messages
MessageFold holds a long message to 20rem with a fade and Show more. It measures, so content that
fits is left alone. Open a turn streamed in this session with defaultOpen: only history folds, and
an answer never shuts while someone is reading it.
Line 1 of the BOQ matched a 4000 K IP65 downlight.
Line 2 of the BOQ matched a 4000 K IP65 downlight.
Line 3 of the BOQ matched a 4000 K IP65 downlight.
Line 4 of the BOQ matched a 4000 K IP65 downlight.
Line 5 of the BOQ matched a 4000 K IP65 downlight.
Line 6 of the BOQ matched a 4000 K IP65 downlight.
Line 7 of the BOQ matched a 4000 K IP65 downlight.
Line 8 of the BOQ matched a 4000 K IP65 downlight.
Line 9 of the BOQ matched a 4000 K IP65 downlight.
Line 10 of the BOQ matched a 4000 K IP65 downlight.
Line 11 of the BOQ matched a 4000 K IP65 downlight.
Line 12 of the BOQ matched a 4000 K IP65 downlight.
Line 13 of the BOQ matched a 4000 K IP65 downlight.
Line 14 of the BOQ matched a 4000 K IP65 downlight.
Line 15 of the BOQ matched a 4000 K IP65 downlight.
Line 16 of the BOQ matched a 4000 K IP65 downlight.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
summary* | React.ReactNode | — | The collapsed line: "3 steps · Searched meetings, read 2 notes". |
className | string | — | Classes for the root. |
Data attributes and CSS variables on MessageSteps
| Attribute | Values |
|---|---|
data-slot | "message-steps" | "message-steps-content" | "message-steps-trigger" |
| Prop | Type | Default | Description |
|---|---|---|---|
label* | React.ReactNode | — | What the step does or did: "Searching tasks…", "Searched tasks". |
error | React.ReactNode | — | Why a failed step failed, under the chip in muted text. |
meta | React.ReactNode | — | A short result after the label: "2 results". |
status | MessageStepStatus | "done" | |
statusLabels | Partial<Record<MessageStepStatus, string>> | { running: "Running", done: "Done", failed: "Failed", stopped: "Stopped" } | The words a screen reader hears for each status, since the icon is what shows it. |
Data attributes and CSS variables on MessageStep
| Attribute | Values |
|---|---|
data-slot | "message-step" | "message-step-error" |
data-status | mirrors a prop or state value |
| Prop | Type | Default | Description |
|---|---|---|---|
items* | readonly MessagePlanItem[] | — | The checklist, in order; the agent updates it in place. |
stateLabels | Partial<Record<MessagePlanItemState, string>> | { todo: "To do", doing: "In progress", done: "Done", failed: "Failed", stopped: "Stopped" } | The words a screen reader hears for each state, since the icon is what shows it. |
Data attributes and CSS variables on MessagePlan
| Attribute | Values |
|---|---|
data-slot | "message-plan" | "message-plan-item" |
data-state | mirrors a prop or state value |
| Prop | Type | Default | Description |
|---|---|---|---|
action | React.ReactNode | — | The row's action, at its end: a small Button such as Undo. |
icon | React.ReactNode | — | Replaces the check. |
Data attributes and CSS variables on MessageChange
| Attribute | Values |
|---|---|
data-slot | "message-change" | "message-change-action" |
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Sources" | The word before the chips, which also names the group. |
Data attributes and CSS variables on MessageSources
| Attribute | Values |
|---|---|
data-slot | "message-sources" |
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Message actions" | Names the group of buttons. |
reveal | "always" | "hover" | "hover" | hover — shown while the pointer is over the message or focus is inside it, and always on a
touch screen. always — for the last message, where the next step is likely one of these. |
Data attributes and CSS variables on MessageActions
| Attribute | Values |
|---|---|
data-reveal | mirrors a prop or state value |
data-slot | "message-actions" |
| Prop | Type | Default | Description |
|---|---|---|---|
current* | number | — | The version on screen, counting from 1. |
total* | number | — | How many versions there are. |
formatPosition | ((current: number, total: number) => string) | (current, total) => `${current} of ${total}` | The position between the buttons. |
label | string | "Versions" | Names the control. |
nextLabel | string | "Next version" | The › button's name. |
onNext | (() => void) | — | Go to the later version. |
onPrevious | (() => void) | — | Go to the earlier version. |
previousLabel | string | "Previous version" | The ‹ button's name. |
Data attributes and CSS variables on MessageVersionSwitch
| Attribute | Values |
|---|---|
data-slot | "message-version-switch" | "message-version-switch-position" |
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | false | Start with the whole message showing. A turn streamed in this session starts open, so an answer never folds up while someone is reading it; only history folds. |
onOpenChange | ((open: boolean) => void) | — | Called when Show more or Show less is pressed, or focus reaches folded content. |
open | boolean | — | Controlled: the whole message shows. Pair with onOpenChange. |
showLessLabel | string | "Show less" | |
showMoreLabel | string | "Show more" |
Data attributes and CSS variables on MessageFold
| Attribute | Values |
|---|---|
data-folded | "" |
data-slot | "message-fold" | "message-fold-content" |
MessageStatus and MessageNotice take StatusLine's props.
Accessibility
- No part is a live region unless you pass
live. A conversation isrole="log", so a part that also announced itself would be read twice. - A step's status and a plan item's state are read out from
statusLabelsandstateLabels, since the icon alone shows them. MessageActionskeeps its buttons in the Tab order while hidden: focus reveals them.- Tabbing to a link inside a folded message opens the fold.
| Key | Action |
|---|---|
| Tab | Move through the steps line, links, actions and versions |
| Enter / Space | Open the steps, press an action, Show more or Show less |
| Contract | States tested |
|---|---|
| Behaviour | steps-closed, steps-open, step-running, step-done, step-failed, step-stopped, plan-todo, plan-doing, plan-done, plan-failed, plan-stopped, status-progress, status-error, notice, change, handoff, actions-hover, actions-always, version-ends, single-version, folded, unfolded, fits, focus-opens-fold |
| Accessibility | named-groups, state-in-words, silent-by-default, live-opt-in, browser-accessibility-test |
| Visual | turn, streaming, steps, plan, changes, notices, versions, fold |