Skip to content

HyInlineEdit ​

Usage examples on this page are written for Lit / plain HTML (<hy-inline-edit>). The same component ships as HyInlineEdit in @whitespaceux/harmony-react (native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.

Click-to-edit text: a display ⇄ edit swap with an async commit lifecycle.

The value of this component is encapsulating the error-prone commit logic — Enter / Escape / blur-commit with a relatedTarget guard, optional confirm/cancel buttons, async validation that keeps the edit view open, and an optimistic-commit-then-rollback contract via a cancelable change event. It is editor-agnostic: the default editor is hy-text-input, but any hy-* form control can be slotted into editor.

Key Features:

  • trigger: click / dblclick / manual (pencil button or programmatic)
  • affordance: hover / always / none — none drops the pencil and makes the text itself the control (Tab, Enter / Space, a hover outline the focus ring recolors)
  • hint: a tooltip over the display view ("Select to rename") — the spoken cue where no glyph says the text is editable
  • emphasis: outlined (the hy-text-input control) / plain (seamless: a bare input in the text's own box — same font, same line, width = its text — so the display ⇄ edit swap moves nothing; the Google Docs title)
  • submit-on: blur / enter / confirm (Escape always reverts)
  • Editor-agnostic editor slot (defaults to hy-text-input)
  • validate keeps the edit view open with an inline error
  • Value states, as on every form control: error / error-message, warning / warning-message, helper-text — one line under the field (error, else warning, else helper text), shown at rest and while editing
  • loading + cancelable change → optimistic update with rollback
  • Display ≠ edit: the default slot renders formatted display; the editor edits the raw value

When to use

  • Renaming or retitling a thing where it is shown: a document title, a list name, a cell value.
  • A value people read far more often than they change.

Do

  • Keep it to one value saved on its own.
  • Use submit-on="confirm" when a change has effects beyond the value itself.

When not to use

  • Several fields saved together: a form of hy-text-input and other fields with a submit hy-button.
  • A change that is destructive or hard to undo: an explicit hy-button, not an edit in place.
  • A value people must notice they can edit to finish a task: a visible hy-text-input or a labeled hy-button.
  • Long or multi-paragraph text: hy-text-area or hy-markdown-editor.
  • An edit that a click elsewhere must not save: submit-on="confirm".

Accessibility: your part ​

  • Set label to say what the value is; without it the editor is only called "Edit value".
  • To hide the pencil, use affordance="none" with a hint: the text itself stays the keyboard way in.

Examples ​

Basic rename ​

html
<hy-inline-edit value="Untitled"></hy-inline-edit>

Renamable title in a header — text only, no buttons, no layout shift (Google Docs) ​

html
<!-- The text style sits on the HOST, in a class of your own reading a Page
text style (--hy-page-heading-xs-*), so the plain editor inherits it too. -->
<hy-inline-edit
  class="doc-title"
  value="Untitled"
  affordance="none"
  emphasis="plain"
  submit-on="blur"
  hint="Select to rename"
  label="Document name"
></hy-inline-edit>

Error and warning at rest ​

html
<hy-inline-edit
  value="Q3 plan"
  label="Name"
  error-message="A board with this name exists."
></hy-inline-edit>
<hy-inline-edit
  value="tmp"
  label="Name"
  warning-message="Short names are hard to find."
></hy-inline-edit>
<hy-inline-edit
  value="Q3 plan"
  label="Name"
  helper-text="Shown to everyone on the team."
></hy-inline-edit>

Async commit with rollback ​

html
<hy-inline-edit value="Untitled" submit-on="blur"></hy-inline-edit>
<script>
  el.addEventListener('change', (e) => {
    e.preventDefault(); // keep the edit view open
    el.loading = true;
    save(e.detail.value).then(
      () => {
        el.value = e.detail.value;
        el.editing = false;
        el.loading = false;
      },
      () => {
        el.loading = false;
      }, // stay open on failure
    );
  });
</script>

API ​

Properties ​

PropertyAttributeTypeDefaultDescription
valuevaluestring | undefined—The committed value.
editingeditingbooleanfalseControlled open state of the edit view. Opening it after the first render (the user, or code setting it later) moves focus into the editor; a field rendered already open does not take focus, so an open row on a static page leaves the page's focus alone.
triggertrigger'click' | 'dblclick' | 'manual''click'How edit mode opens.
submitOnsubmit-on'blur' | 'enter' | 'confirm''blur'When a draft is committed.
affordanceaffordance'hover' | 'always' | 'none''hover'When the edit affordance (pencil) is visible. hover reveals it on hover / focus (ideal for dense lists where a persistent pencil per row is clutter); always keeps it visible at rest (prominent single fields where editability must be discoverable); none renders no pencil at all and makes the text itself the control — a button reachable by Tab, opened by click / Enter / Space, an outline on hover, recolored by focus, standing in for the glyph, focus handed back to it after Enter / Escape / the confirm buttons. The shape of a document or page title in a header (Google Docs, Notion, Atlassian's inline edit); it needs a trigger other than manual to be reachable.
loadingloadingbooleanfalseAsync pending — shows a spinner, disables the editor.
placeholderplaceholderstring | undefined—Empty-value placeholder shown in the display view.
disableddisabledbooleanfalseWhether the control is disabled.
labellabelstring | undefined—Accessible label forwarded to the default editor.
emphasisemphasis'outlined' | 'plain''outlined'The default editor's chrome. outlined (default) is the hy-text-input control — a full form control with its own height and padding. plain is the seamless editor: a bare input inheriting the host's font, size, weight, letter-spacing and line-height, no padding / border / background, width sized to its text (a hidden mirror span), the focus ring its only chrome — so the display ⇄ edit swap changes nothing around it. A click opens it with the caret where the click landed; with affordance="none" keyboard focus opens it directly with everything selected (Tab onto the name and type to replace — no Enter first). Put the typography on the host (not on a slotted span) so both views read it. A slotted editor wins over either.
hinthintstring | undefined—Tooltip shown over the display view on hover / focus (e.g. "Select to rename"). The display view is wrapped in an hy-tooltip only while the hint is set, and never while editing. Pairs with affordance="none", where no glyph says the text is editable; the hint also becomes the trigger's accessible description, alongside the message line (error, warning or helper text) — the trigger is described by both.
validate—InlineEditValidator | undefined—Validator run before commit; a returned message keeps the edit view open and becomes the error state — written to error-message, so it shows exactly as an error the app sets. Editing the draft, canceling or closing clears it (and puts back an error the app had set). Property-only.
errorerrorbooleanfalseError state: the value is not accepted and has to change. Paints the danger color (the ring around the text at rest, the editor while editing), sets aria-invalid on the editor and shows error-message. Setting error-message raises it; clearing it lowers it.
errorMessageerror-messagestring''The error line under the field (replaces the helper text). Setting it raises error.
warningwarningbooleanfalseWarning state: the value is accepted but worth a second look. Paints the warning color at rest only (focus keeps the normal ring) and shows warning-message; never sets aria-invalid. error wins when both are set.
warningMessagewarning-messagestring''The warning line under the field (replaces the helper text). Setting it raises warning.
helperTexthelper-textstring''The helper line under the field, shown when no error or warning message is.
editLabeledit-labelstring'Edit'Accessible label for the pencil button. Use for internationalization.
editorLabeleditor-labelstring'Edit value'Accessible label for the default editor when label is unset. Use for internationalization.
saveLabelsave-labelstring'Save'Accessible label for the confirm button (submit-on="confirm"). Use for internationalization.
cancelLabelcancel-labelstring'Cancel'Accessible label for the cancel button (submit-on="confirm"). Use for internationalization.
savingLabelsaving-labelstring'Saving'Accessible label for the spinner while loading. Use for internationalization.

Events ​

EventDetailDescription
edit-start—Fired when the edit view opens. Detail: {}
change—Commit. Cancelable — preventDefault() keeps the edit view open. Detail: { value: string }
cancel—Fired when editing is canceled (Escape / cancel button). Detail: {}

Slots ​

SlotDescription
defaultThe display rendering of the rest state (formatted). Falls back to value / placeholder.
editorThe edit control (defaults to hy-text-input, or a bare input under emphasis="plain"); a slotted editor wins over both.

CSS Parts ​

PartDescription
displayThe display-view wrapper.
editThe edit-view wrapper.
helper-textThe helper line under the field.
error-messageThe error line under the field.
warning-messageThe warning line under the field.
message-iconThe icon starting the error or warning line.

CSS Custom Properties ​

The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.

PropertyDefaultDescription
--hy-inline-edit-foreground-danger-restvar(--hy-foreground-danger-rest)Error line and error icon color.
--hy-inline-edit-foreground-warning-restvar(--hy-foreground-warning-rest)Warning line, warning ring and warning icon color.
--hy-inline-edit-foreground-subtle-restvar(--hy-foreground-subtle-rest)Helper line color.
--hy-inline-edit-message-gap-layout-2xsvar(--hy-gap-layout-2xs)Gap between the message icon and its text.
--hy-inline-edit-editor-min-size—Floor under an empty draft in the seamless editor, so the field stays clickable (4ch unless set)

Built with Lit. Documented with VitePress.