Edit message
Turns a sent message back into an editor so people can fix it and resend.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/edit-message.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/edit-message.json
Usage & API
Example
import { EditMessage } from "@/components/ai/edit-message"
<EditMessage
text="…"
onSave={() => {}}
editing={true}
onEditingChange={() => {}}
/>Props EditMessage
| Prop | Type & description | Required |
|---|---|---|
text | string | Required |
onSave | (text: string) => void | Required |
editing | boolean | Required |
onEditingChange | (editing: boolean) => void | Required |
defaultDraft | stringStarting text for the editor if it mounts already open. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Default exports EditMessage. The closed button is Edit message, shown when the group is hovered or when the button itself is focused, so keyboard users are not stuck with an invisible control. Opening sets a ref that allows the focus effect to run, selects to the end of the textarea, and the effect that watches editing copies text into the draft only on the transition from closed to open. Mounting with editing true and a defaultDraft skips that copy, because the previous editing value starts as true. The demo uses that path: text is a question about 8 people, defaultDraft changes it to 12 people and adds Two are vegetarian, and editing starts true. Save and resend is a submit button. Cancel is a button beside it. The form's entrance scales from slightly smaller, origin at the top right, matching the bubble's corner. aria-label on the textarea is Edit message, the same words as the pencil, so the name does not change between the two states in a confusing way. It is a different element, though, and focus is moved on purpose.
Choosing a layout
Use it on the person's message, not on the assistant's. Branch picker is how you step through assistant versions without changing the question. Edit message is how the question itself changes. After onSave, drop branches that belonged to the old text, or the picker will show answers to a question that is no longer on screen. Message actions stay on the assistant turn. They regenerate from the same prompt. They do not open this textarea.
Keep editing in your state so a send from Prompt input can close it, and so only one message is open. The component will not close its siblings. If you leave two editors open, both will call onSave on Enter. The note about later replies is copy, always visible while editing, not a check you can turn off. If your product keeps the later replies and only forks, you should not use this component as is, because the sentence would be wrong. Pair it with a thread that truncates after the edited id.
I pressed Save and the assistant answer is still the old one.
onSave only receives the trimmed draft. The file's own demo stores that string, sets a thinking flag, and swaps the reply after 1.1 seconds. You do the same with a real request. The line Replies after this will be replaced is a warning, not a deletion. Remove or ignore assistant messages that followed this user message, then stream a new one. Branch picker indexes will be stale if you keep the old nodes mounted.
Why does the editor open with different text than the bubble I clicked?
If editing is already true on the first render, the reset effect does not run, and defaultDraft is what you see. That is the demo's setup. After a real open, the draft is copied from text each time editing flips to true. Closing without saving throws the draft away. A save requires a trimmed difference, so changing 8 people to 8 people with extra spaces does not enable the button. Enter inside a composition, or with Shift, will not save either.