Inline Edit Field
A click-to-edit inline field — the display and edit states swap seamlessly with a layout morph, Enter/blur submits and Esc cancels, with multiline support, controlled and uncontrolled modes, and no accidental submit mid-IME-composition.
npx shadcn@latest add https://webberui.com/r/inline-edit-field.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<InlineEditField />
Installation
npx shadcn@latest add https://webberui.com/r/inline-edit-field.jsonOr, once registries are configured in components.json, install it as @webberui/inline-edit-field.
Usage
import { InlineEditField } from "@/components/ui/inline-edit-field";
<InlineEditField
value={title}
onValueChange={setTitle}
placeholder="Untitled event"
aria-label="Event title"
/>When value is omitted, the component manages the state internally (uncontrolled mode) and defaultValue supplies the initial value; passing value together with onValueChange puts it in controlled mode. onValueChange only fires on "submit" (Enter, blur, or pressing ✓), never on every keystroke.
Multiline mode:
<InlineEditField
multiline
maxRows={5}
variant="outline"
defaultValue="Add a short intro⋯"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value; when omitted, the component manages it internally |
defaultValue | string | "" | Initial value in uncontrolled mode |
onValueChange | (value: string) => void | — | Fires on submit (Enter / blur / pressing ✓) with the latest value |
onEditingChange | (editing: boolean) => void | — | Fires when the editing state toggles |
placeholder | string | "點擊編輯⋯" | Text shown when the value is empty, also used as the input hint |
multiline | boolean | false | Use a multiline textarea (Enter inserts a newline, ⌘/Ctrl+Enter submits) |
maxRows | number | 6 | Maximum rows in multiline mode; beyond that the input scrolls |
disabled | boolean | false | Disabled: read-only display, editing cannot be entered |
submitOnBlur | boolean | true | Whether blurring submits; false treats blur as a cancel |
selectOnEdit | boolean | true | Whether to select all the existing text when entering edit mode |
showActions | boolean | true | Whether to show the ✓ / ✕ action buttons while editing |
variant | "ghost" | "outline" | "ghost" | Display style: ghost (background only on hover/focus) or outline (a permanent border) |
aria-label | string | — | Accessible label; falls back to placeholder when omitted |
className | string | — | Appended to the outermost container |
inputClassName | string | — | Appended to the input element (input / textarea) |
How it works
- Click to edit: the display state is a button occupying the same position and font size as the text; clicking it (or Tab-focusing it and pressing Enter / Space) swaps it for an input, and a pencil icon appears on the right on hover to signal that it is editable
- Seamless morph: the display button and the edit area cross-fade through
AnimatePresence mode="popLayout", while the outer container carrieslayoutso a spring smoothly makes up the size difference and the text never jumps out of position; the edit state's border, background, and focus ring fade in with a CSS transition - Submit and cancel: single-line presses Enter to submit, Esc to cancel, and blur submits by default (
submitOnBlur); multiline uses Enter for a newline, ⌘/Ctrl+Enter to submit, and Esc to cancel.onValueChangedoes not fire when the value is unchanged - Action buttons do not jump the gun: ✓ / ✕ block the default behaviour with
onMouseDownto keep the input focused, avoiding a race where the click first triggers a blur submit - Multiline auto-grow:
multilinegrows with the content and scrolls inside the box pastmaxRows(using the textarea's own auto-resize) - IME safe: pressing Enter mid-composition in Chinese and other input methods does not accidentally submit (it checks both
isComposingandkeyCode !== 229, covering Safari's habit of firing keydown only aftercompositionend)
Accessibility
- When the user has "reduce motion" enabled at the system level, all morph animations are disabled and display/edit switch instantly (the first render matches SSR; it is only disabled after mount)
- The display state is a native
<button>with anaria-labeldescribing the value, takes Tab focus in order, and shows a ring onfocus-visible - The input carries an
aria-label(falling back toplaceholderwhen omitted) - When editing ends via the keyboard or the action buttons, focus returns to the display button; when it ends because of a blur, focus is not stolen back, so it does not interfere with whatever the user actually clicked
- ✓ / ✕ are native
<button>s, each with a "confirm" and "cancel"aria-label