Inline Reply
betaA reply or an edit written in place: a soft line that springs open into a field. Enter sends, and Esc cancels.
Preview
Team sync is Tuesday at 6.
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/inline-reply
Use it for
- Replying under a message, a comment, or a post, right where it is.
- Editing your own message in place, with mode edit and the message in defaultValue.
- Open it from a Reply button with open and onOpenChange. The cursor goes in.
Avoid it for
- A new message to a group. Use Composer, which says who it reaches.
- Long, formatted writing. Use Editor.
- A form field that waits for a Save button. Use Textarea.
Props
- placeholder
The collapsed line's words and the field's name, like "Reply to Design".
string · default "Reply" or "Edit message"
- defaultValue
Words to start with, like the message being edited.
string
- open
Controlled: the field shows while true. Opening it this way, or rendering it with open, puts the cursor in it.
boolean
- defaultOpen
Starts open, without taking focus.
boolean · default false
- onOpenChange
Hears every open and close: a press on the line, a send, or Esc.
(open: boolean) => void
- autoFocus
Puts the cursor in it on first render when it starts open.
boolean · default false
- mode
Edit keeps the sent words in the field for the next edit.
"reply" | "edit" · default "reply"
- onSend
Enter sends the words, trimmed. Empty words are never sent.
(text: string) => void
- onCancel
Esc took it back. The words go back to defaultValue.
() => void
- size
Height and padding, as other fields.
"sm" | "md" | "lg" · default "md"
- disabled
Turns it off.
boolean · default false
- className
Extra classes on the root. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| placeholder | string | "Reply" or "Edit message" | The collapsed line's words and the field's name, like "Reply to Design". |
| defaultValue | string | - | Words to start with, like the message being edited. |
| open | boolean | - | Controlled: the field shows while true. Opening it this way, or rendering it with open, puts the cursor in it. |
| defaultOpen | boolean | false | Starts open, without taking focus. |
| onOpenChange | (open: boolean) => void | - | Hears every open and close: a press on the line, a send, or Esc. |
| autoFocus | boolean | false | Puts the cursor in it on first render when it starts open. |
| mode | "reply" | "edit" | "reply" | Edit keeps the sent words in the field for the next edit. |
| onSend | (text: string) => void | - | Enter sends the words, trimmed. Empty words are never sent. |
| onCancel | () => void | - | Esc took it back. The words go back to defaultValue. |
| size | "sm" | "md" | "lg" | "md" | Height and padding, as other fields. |
| disabled | boolean | false | Turns it off. |
| className | string | - | Extra classes on the root. Later classes win. |
Accessibility
- Collapsed, it is a button named by placeholder. Open, it is a textarea with the same name.
- The field is described by a hidden line: Enter sends, Shift+Enter starts a new line, Esc cancels.
- Esc cancels it and goes no further, so a sheet or a row around it stays.
- After a send or Esc from inside, focus goes back to the line, unless something around it took focus first.
- In forced colors, the collapsed line keeps an edge, since its gray fill is gone.
Examples
Under a post
<InlineReply placeholder="Reply to Design" onSend={(text) => reply(text)} />Opened by a Reply button
const [replying, setReplying] = useState(false);
<HoverActions actions={[{ label: "Reply", icon: <MessageCircle />, onClick: () => setReplying(true) }]} />
{replying && <InlineReply placeholder="Reply to Anna" open onOpenChange={setReplying} onSend={reply} />}