Inline Edit

Click-to-edit text that keeps your draft when a save fails or you cancel, and warns when someone else changed the value mid-edit.

StopsEdit lost or overwritten silently

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/inline-edit.json

The files land in your project. They're yours to edit.

Usage

inline-edit-usage.tsx
import { InlineEdit } from "@/components/ui/inline-edit"

<InlineEdit
  label="Project name"
  value={name}
  onValueChange={setName}
  validate={(value) => (value.length < 3 ? "Use at least 3 characters." : undefined)}
  onSave={async (value) => {
    await new Promise((resolve) => setTimeout(resolve, 600))
    if (/fail/i.test(value)) throw new Error("The server said no. Your text is still here.")
  }}
/>

What it catches

  • Edit thrown away by Escape: a Restore button brings it back
  • Save failure: the draft is kept and the error shown
  • Value changed elsewhere while you edit: conflict shown with Use theirs
  • Validation errors block saving

Props

InlineEdit props
PropTypeDefault
label* (required)

Accessible name of the field, e.g. "Project name".

string—
value

Controlled saved value.

string—
defaultValue

Initial saved value when uncontrolled.

string""
onValueChange

Called with the new value once it is saved.

(value: string) => void—
onSave

Persists the value. Return a promise to show a saving state; throw to keep editing with your draft.

(value: string) => void | Promise<void>—
validate

Return an error message to block saving, or nothing when the value is fine.

(value: string) => string | undefined—
inputClassName

Extra classes for the input while editing.

string—
hint

Hint shown while editing.

string"Enter to save · Esc to cancel"
discardedMessage

Notice shown after Escape threw away an edit.

string"Edit discarded."
restoreLabel

Label of the button that brings a discarded edit back.

string"Restore"
useTheirsLabel

Label of the button that takes the value someone else saved meanwhile.

string"Use theirs"
saveErrorMessage

Fallback error when `onSave` rejects without a message.

string"Couldn't save. Try again."
conflictMessage

Shown when someone else saved while you were editing. `{value}` is their value.

string"Changed to “{value}” while you were editing. Enter keeps…

* required. Also accepts the native props of <input> (className, aria-*, data-*…), passed through to it.

Accessibility

  • The read view is a button whose aria-label names the field, current value and Edit
  • Input has an aria-label, aria-invalid on error and a hint in a polite live region
  • Enter saves and Escape cancels; focus returns to the trigger after closing
  • Saving spinner has an aria-label

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Conflict detection compares the value prop; it needs your app to push remote changes into it
  • Not a merge tool: you choose one version
  • No offline queue for failed saves

What it stops

Edit lost or overwritten silently

Also known as

inline edit · click to edit · editable · contenteditable · rename · table cell edit · optimistic save · kanban title

Field type

Dependencies

Pairs well with