composed · Composed pattern
InlineEdit
@devalok/shilp-sutra/composed/inline-editView in Storybook Hand-curated previews ship in rolling waves. See it live in Storybook →
Reference
Props
value: string (current text value)
onSave: (newValue: string) => void | Promise<void> (called on commit; async shows spinner)
placeholder: string (shown when value is empty)
textClassName: string (CSS class for the editable text, e.g. "text-ds-lg font-semibold")
readOnly: boolean
maxLength: number
saving: boolean (external saving state — shows spinner, disables editing)
Defaults
placeholder="Click to edit", readOnly={false}, saving={false}
Example
<InlineEdit
value={title}
onSave={(v) => updateTitle(v)}
textClassName="text-ds-lg font-semibold"
/>
Composability
- contentEditable-based — the text IS the editor (Notion / Linear / Figma layer-name pattern). No separate input field appears.
- Keyboard contract: Click to focus (auto-selects text, like Finder rename). Type to edit. Enter saves, Escape reverts. Paste strips rich content.
- Async save:
onSavecan return a Promise — InlineEdit shows a spinner and disables editing while pending. On rejection, text reverts to the original value automatically. - Accessibility: Accepts
aria-labelandaria-labelledby(forwarded to the role="textbox" span) — required when the text isn't self-descriptive. Falls back toplaceholderas label when neither is provided. - Not inside FormField — InlineEdit is for in-place editing of existing content (task title, project name); use regular FormField + Input for traditional forms.
- textClassName for typography control: Pass
"text-ds-lg font-semibold"to make it look like a heading without changing the underlying element.
Gotchas
- Uses contentEditable — the text IS the editor. No input field appears.
- Click to focus → cursor appears in text. Type to edit. Enter saves. Escape reverts.
- Text is auto-selected on focus (like renaming a file in Finder)
- Paste is restricted to plain text (no rich content)
- The value is trimmed before calling
onSave; if unchanged,onSaveis not called - If
onSavereturns a Promise, a spinner is shown and editing is disabled until it resolves - On Promise rejection, the text reverts to the original value