Skip to content

Components · Forms

InlineEditText

Text that edits in place: async save, pending and error states, validation.

@rookcom/monarch-reactComponent

Title with validation

Edit

Usage examples

Failed save keeps the draft

Multiline with actions

Purpose

Lets people rename or rewrite a short piece of text where it is shown, such as a record title or a description, without opening a form. The product supplies persistence; the component handles the edit cycle and its states.

Use when

  • A single text value on a detail page should be editable in place: an issue or ticket title, a category name, a description.
  • The save is per field and can fail, so the user needs a pending state and an error that keeps their draft.

Avoid when

  • Several fields are edited and saved together: use a form with Field and an explicit Save.
  • The value is not text (dates, people, options): use the matching picker.
  • Rich text with formatting or mentions: that editor stays in the product.

Design principles

  • The text looks like text until it is hovered or focused; a pencil icon and a hover surface reveal that it is editable.
  • View and edit modes share padding and border width, so switching does not move the text. Font size and weight are inherited, so it fits a page title or a table cell.
  • A failed save never loses work: the editor stays open with the draft and the error.
  • No business logic: saving, validation rules and conflict handling are callbacks from the product.

API reference

import { InlineEditText } from '@rookcom/monarch-react';
ExportKindRenderingRoot class
InlineEditTextcomponentclient.mn-inline-edit-

Renders <div class="mn-inline-edit-text"> (ref) containing a <button> in view mode, or an <input> (<textarea> with multiline) in edit mode.

onSave(next) may return a promise. While it is pending the input is read-only and a spinner shows; if it rejects, the editor stays open and getErrorMessage(error) is shown.

Edit conflicts stay in the product: handle them in onSaveError(error, draft) and pass a message (or your own resolution UI) through error while keeping editing true.

Generated from the TypeScript declarations of packages/react/src/components/forms/inline-edit-text.

Properties

InlineEditText

Text that turns into an input in place: click it or press Enter to edit, Enter (or leaving the field) saves, Escape cancels. Saving can be async, with a pending state, and a failed save keeps the editor open with the error. The product owns persistence and conflict handling.

PropTypeDescription
label requiredstringWhat the text is, for example "Title". The input's accessible name, and part of the view button's.
onSave required(next: string) => void | Promise<void>Persists a new value. Return a promise to show the pending state; reject it (throw) to keep the editor open with the error and the user's draft. Not called when the value is unchanged.
cancelLabelstring
classNamestring
defaultEditingbooleanInitial edit mode (uncontrolled).
defaultValuestringThe initial saved value (uncontrolled).
disabledbooleanNot editable: the value is shown as plain text.
editHintstringVisually hidden hint read after the view button's name. Default "Edit".
editingbooleanEdit mode (controlled).
errorReactNodeAn error supplied by the product (for example a conflict notice), shown while editing.
getErrorMessage((error: unknown) => ReactNode)Turns a rejected save into a message. Default: the Error's message, or "Couldn't save. Try again."
idstring
maxLengthnumber
multilinebooleanA textarea instead of an input: Enter adds a line, Ctrl/⌘+Enter saves.
onCancel(() => void)Called when the user cancels with Escape or the Cancel button.
onEditingChange((editing: boolean) => void)
onSaveError((error: unknown, draft: string) => void)Called when `onSave` rejects, with the error and the draft that failed. Use it for product-specific handling such as an edit conflict (HTTP 409): keep the editor open and pass your own resolution UI through `error`.
placeholderstringShown in view mode when the value is empty, and as the input placeholder.
rowsnumberRows for `multiline`. Default 3.
saveLabelstring
saveOnBlurbooleanSave when focus leaves the editor. Default true.
savingLabelstringAccessible name of the pending spinner. Default "Saving".
showActionsbooleanShow Save and Cancel buttons under the editor. Default: true for `multiline`.
trimbooleanTrim whitespace before comparing and saving. Default true.
validate((draft: string) => ReactNode | null | undefined)Validates the draft before saving. Return a message to block the save, or nothing to allow it.
valuestringThe saved value (controlled).

Variants

PropValuesDescription
multiline'true''false'A textarea that grows with its content (field-sizing: content where supported). Enter adds a line; Ctrl/⌘+Enter saves. Shows Save and Cancel buttons by default (data-multiline).
showActions'true''false'Save and Cancel buttons under the editor. Default true for multiline.
saveOnBlur'true''false'Save when focus leaves the editor (default). Turn off when only explicit saves should count.

States

StateExposed asBehaviour
Viewingdata-state="viewing"A button showing the value, or the placeholder in subtle text when empty (data-empty).
Editingdata-state="editing"An input with the draft, focused with its text selected.
Savingdata-saving, aria-busy="true", readonlyThe input is read-only with a spinner named "Saving"; with actions, the Save button shows its loading state.
Errordata-invalid, aria-invalid="true"A validation message, a failed save, or the product’s error. Shown under the input with role="alert" and linked by aria-describedby.
Disableddata-disabledPlain text, not a button: nothing to activate.

Interaction behaviour

  • Click the text, or focus it and press Enter or Space, to edit.
  • Enter saves (Ctrl/⌘+Enter with multiline); Escape cancels and restores the saved value; leaving the field saves unless saveOnBlur is false.
  • Whitespace is trimmed (trim); an unchanged value closes the editor without calling onSave.
  • validate(draft) runs before saving; a message blocks the save and keeps the editor open.
  • A failed save keeps the draft and the error. Leaving the field does not resend the same failed draft; editing it, Enter or Save tries again.
  • After Enter, Escape, Save or Cancel, focus returns to the text. After a blur save, focus stays where the user moved it.
  • Controlled: value + onSave, and optionally editing + onEditingChange. Uncontrolled: defaultValue, defaultEditing.

Keyboard support

KeysAction
TabMoves to the text (a button), or through the input and its buttons.
Enter / SpaceOn the text: starts editing.
EnterIn a single-line editor: saves.
Ctrl/⌘ + EnterIn a multiline editor: saves.
EscapeCancels the edit and returns focus to the text.

Enter is ignored while an input method editor is composing text.

Accessibility

Role
button (view), textbox (edit)
Accessible name
label: the input is named by it, and the view button reads "{label}: {value}" with an "Edit" description (editHint).
  • Edit mode is not hidden behind hover: the text itself is the keyboard-reachable control.
  • Errors use role="alert" and are linked to the input with aria-describedby while it is aria-invalid.
  • The saving spinner is a named status ("Saving"); the input is aria-busy and read-only, not disabled, so focus stays in place.
  • The pencil icon is decorative.

Responsive behaviour

  • Fills its container and wraps long values (overflow-wrap: anywhere); no horizontal scrolling at 320px.
  • On touch devices (hover: none) the pencil icon is always visible, since there is no hover to reveal it.
  • Action buttons wrap under the editor on narrow screens.

Theme support

  • Surfaces, borders and text use Monarch semantic tokens, so light, dark and more-contrast themes apply automatically.
  • The focus ring uses --mn-color-focus-ring (the product accent); errors use --mn-color-danger-strong.
  • Transitions use the duration tokens, which are zero under prefers-reduced-motion.

Do and don't

Do

  • Give every instance a label that names the value ("Title", "Category name").
  • Return the save promise from onSave, so the pending and error states are real.
  • Throw an Error with a message that says what to do next.
  • Keep conflict detection in the product and show it through error.

Don't

  • Do not use it for values that must be saved together with other fields.
  • Do not swallow save errors and resolve the promise: the user would believe it saved.
  • Do not hide the only edit affordance behind hover; the text itself is the control.
  • Input: A plain, always-editable input inside a form.
  • Field: Label, help and error wiring for form controls.
  • RecordHeader: Planned record header with an inline-edit title.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsButton, Spinner

Testing coverage

  • View mode is a button named "{label}: {value}" with an "Edit" description.
  • Click to edit focuses the input; Enter saves trimmed text and returns focus (uncontrolled).
  • Enter on the text starts editing; Escape cancels, calls onCancel and restores focus.
  • Blur saves without stealing focus back; an unchanged value does not call onSave.
  • Async save: aria-busy, read-only and a "Saving" status; on rejection the draft, aria-invalid, the alert and focus are kept, and onSaveError gets the error and draft.
  • A failed draft is not resent on blur, but is retried after an edit.
  • validate blocks the save and describes the input.
  • Controlled value and editing follow the parent; an external error shows while editing.
  • Multiline: Enter adds a line, Ctrl+Enter saves, Save and Cancel buttons work; tabbing to the buttons does not save.
  • Disabled renders plain text; an empty value shows the placeholder; server rendering in both modes.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery; Cross-browser axe: /components/inline-edit-text

Tests: packages/react/src/components/forms/inline-edit-text