WebberUI

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.

Loading preview…
npx shadcn@latest add https://webberui.com/r/inline-edit-field.json

Playground

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.json

Or, 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

PropTypeDefaultDescription
valuestringControlled value; when omitted, the component manages it internally
defaultValuestring""Initial value in uncontrolled mode
onValueChange(value: string) => voidFires on submit (Enter / blur / pressing ✓) with the latest value
onEditingChange(editing: boolean) => voidFires when the editing state toggles
placeholderstring"點擊編輯⋯"Text shown when the value is empty, also used as the input hint
multilinebooleanfalseUse a multiline textarea (Enter inserts a newline, ⌘/Ctrl+Enter submits)
maxRowsnumber6Maximum rows in multiline mode; beyond that the input scrolls
disabledbooleanfalseDisabled: read-only display, editing cannot be entered
submitOnBlurbooleantrueWhether blurring submits; false treats blur as a cancel
selectOnEditbooleantrueWhether to select all the existing text when entering edit mode
showActionsbooleantrueWhether 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-labelstringAccessible label; falls back to placeholder when omitted
classNamestringAppended to the outermost container
inputClassNamestringAppended 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 carries layout so 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. onValueChange does not fire when the value is unchanged
  • Action buttons do not jump the gun: ✓ / ✕ block the default behaviour with onMouseDown to keep the input focused, avoiding a race where the click first triggers a blur submit
  • Multiline auto-grow: multiline grows with the content and scrolls inside the box past maxRows (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 isComposing and keyCode !== 229, covering Safari's habit of firing keydown only after compositionend)

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 an aria-label describing the value, takes Tab focus in order, and shows a ring on focus-visible
  • The input carries an aria-label (falling back to placeholder when 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

On this page