Inline edit

Rename in place: the text becomes a field without moving.

pnpm dlx shadcn@latest add @uiarc/inline-edit
Live · keyboard ready

Activate to edit.Enter saves, Escape cancels.
Activate to edit.Enter saves, Shift+Enter adds a line break, Escape cancels.

Last edited by Emma Collins on Mon 15 Sep

  • Titles and descriptions read far more often than edited, like a project name.
  • Single fields that save on their own with optimistic updates and rollback.
  • Use input or textarea in a form when several fields save together.
  • Use input when the field should always look editable.

Installation

Add Inline edit with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/inline-edit

Adds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { InlineEdit } from "@/registry/components/inline-edit/inline-edit"; export function ProjectTitle({ project }: { project: { id: string; name: string } }) {  return (    <InlineEdit      as="h1"      label="Project name"      value={project.name}      validate={(next) => (next.trim() ? null : "Name can’t be empty")}      onSave={(next) => renameProject(project.id, next)}    />  );}

InlineEdit

Click-to-edit text that becomes a field in place with the same metrics, saves optimistically, and rolls back on failure.

PropTypeDefaultDescription
valueRequiredstring–The saved value. Outside changes replace the text while not editing.
onSaveRequired(next: string) => void | Promise<unknown>–Persists the value. Return a promise to show saving; reject it to roll back with a retry.
labelRequiredstring–Accessible name, for example "Project name".
validate(next: string) => string | null | undefined–Returns an error message to block saving.
placeholderstring""Shown when the value is empty.
multilinebooleanfalseWraps and grows in height; Shift+Enter adds a line break.
variant"title" | "body""title"Type style: title for names and headings, body for descriptions.
as"span" | "p" | "h1" | "h2" | "h3""span"Element that holds the text, so a title can stay a heading.
classNamestring–Added to the root.
EnterorSpace
On the text, starts editing.
Enter
Saves the draft. In multiline mode, Shift+Enter inserts a line break.
Escape
Cancels and rolls the text back.
Tab
Leaving the component saves, the way a rename does.
  • The resting text is a native button labelled "<label>: <value>", so it is focusable and announced as editable.
  • The field has aria-label and aria-invalid; validation and save errors render in a polite live region linked through aria-describedby.
  • Save progress and results are announced through a role="status" region; save and cancel buttons are labelled.
  • Text rolls between values, a check draws once a save lands, and multiline boxes follow their height; failed saves roll back with motion.
  • Reduced motion crossfades text, draws the check instantly, and replaces the spinner with a static mark.
  • Text wraps within the column and reserves 66px at the end for the save and cancel buttons.
  • With multiline the box grows in height as the text wraps.
  • Hover hints apply only on fine pointers; on touch a tap starts editing.
  • Two ResizeObservers size the frame; sizes are measured in a layout effect, so no frame shows a wrong width.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for single fields read far more often than edited, like titles and descriptions. Use a regular form with input or textarea when several fields save together.
  • value is controlled by the saved data; update it after onSave resolves. Return a promise from onSave to get saving, saved, and failed states.
  • Exported as both named and default.

The full library index for assistants is at /llms.txt.