Message Actions
A compact row under a chat message. Copy turns the Markdown into plain text and can add formatted HTML; the send time reads as Today, Yesterday, or a date; assistant replies can be rated and regenerated. Message Branch steps between regenerated versions.
Usage
import { MessageActions } from "@tulipfarm/sprout/message-actions";
<MessageActions
text={message.text}
createdAt={message.createdAt}
feedback={message.feedback}
onFeedback={(value) => rate(message.id, value)}
onRetry={() => regenerate(message.id)}
/>Only text is required, which shows a single Copy button. Each other prop adds one part: createdAt the time,onFeedback the Helpful and Unhelpful toggles, andonRetry a Regenerate button. There are no visual variants; icon buttons are ghost icon-sm and Regenerate is a ghostsm button. A given rating uses the secondary fill.
author names the controls: "assistant" (the default) says “response”, "human" says “message”.disabled turns off every button, for example while a reply is still running.
In a conversation
const entry = useRef<HTMLDivElement>(null);
<Message align={message.author === "human" ? "end" : "start"}>
<MessageContent ref={entry}>
{bubbles}
<MessageActions
text={text}
author={message.author}
createdAt={message.createdAt}
hover
html={() => {
const parts = entry.current?.querySelectorAll("[data-slot=message-bubble]");
return parts?.length ? [...parts].map((part) => part.innerHTML).join("") : undefined;
}}
/>
</MessageContent>
</Message>With hover, the row stays hidden until the pointer is over the surrounding Message or anything in it has focus. On touch screens it is always shown. html runs when Copy is pressed; when it returns markup, the clipboard gets both HTML and plain text, so pasting into a document keeps bold, code, and links.
Send time
createdAt is an ISO time shown in the reader’s locale and time zone. Its title gives the full date and time.
Versions
import { MessageActions, MessageBranch } from "@tulipfarm/sprout/message-actions";
<MessageBranch index={index} total={versions.length} onChange={setIndex} />
<MessageActions text={versions[index].text} onRetry={regenerate} />index is zero-based and shown as “2 / 3”. Previous is disabled on the first version and Next on the last.
Copy only
Pass just text under text that is not a chat turn, such as a written handoff in a dialog.
States
After a copy the icon becomes a check and its label says it was copied. If the browser refuses the write, a message under the row asks the reader to select the text instead. Choose Blocked to try it; this demo blocks the clipboard only while that option is chosen.
Accessibility
Actions sit in a group named “Response actions” or “Message actions”; versions in a group named “Response versions”. Icon buttons have labels and titles, and the ratings are toggle buttons witharia-pressed. A successful copy is announced through a status region and a failed one as an alert. The version count is a polite live region. A hover row becomes visible as soon as keyboard focus enters the message. It fades in over 120ms, or appears at once when reduced motion is set.