HyInlineEdit
Usage examples on this page are written for Lit / plain HTML (
<hy-inline-edit>). The same component ships asHyInlineEditin@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—nonedrops 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 editableemphasis:outlined(thehy-text-inputcontrol) /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
editorslot (defaults tohy-text-input) validatekeeps 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+ cancelablechange→ 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-inputand other fields with a submithy-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-inputor a labeledhy-button. - Long or multi-paragraph text:
hy-text-areaorhy-markdown-editor. - An edit that a click elsewhere must not save:
submit-on="confirm".
Accessibility: your part
- Set
labelto say what the value is; without it the editor is only called "Edit value". - To hide the pencil, use
affordance="none"with ahint: the text itself stays the keyboard way in.
Examples
Basic rename
<hy-inline-edit value="Untitled"></hy-inline-edit>Renamable title in a header — text only, no buttons, no layout shift (Google Docs)
<!-- 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
<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
<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
value | value | string | undefined | — | The committed value. |
editing | editing | boolean | false | Controlled 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. |
trigger | trigger | 'click' | 'dblclick' | 'manual' | 'click' | How edit mode opens. |
submitOn | submit-on | 'blur' | 'enter' | 'confirm' | 'blur' | When a draft is committed. |
affordance | affordance | '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. |
loading | loading | boolean | false | Async pending — shows a spinner, disables the editor. |
placeholder | placeholder | string | undefined | — | Empty-value placeholder shown in the display view. |
disabled | disabled | boolean | false | Whether the control is disabled. |
label | label | string | undefined | — | Accessible label forwarded to the default editor. |
emphasis | emphasis | '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. |
hint | hint | string | 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. |
error | error | boolean | false | Error 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. |
errorMessage | error-message | string | '' | The error line under the field (replaces the helper text). Setting it raises error. |
warning | warning | boolean | false | Warning 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. |
warningMessage | warning-message | string | '' | The warning line under the field (replaces the helper text). Setting it raises warning. |
helperText | helper-text | string | '' | The helper line under the field, shown when no error or warning message is. |
editLabel | edit-label | string | 'Edit' | Accessible label for the pencil button. Use for internationalization. |
editorLabel | editor-label | string | 'Edit value' | Accessible label for the default editor when label is unset. Use for internationalization. |
saveLabel | save-label | string | 'Save' | Accessible label for the confirm button (submit-on="confirm"). Use for internationalization. |
cancelLabel | cancel-label | string | 'Cancel' | Accessible label for the cancel button (submit-on="confirm"). Use for internationalization. |
savingLabel | saving-label | string | 'Saving' | Accessible label for the spinner while loading. Use for internationalization. |
Events
| Event | Detail | Description |
|---|---|---|
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
| Slot | Description |
|---|---|
default | The display rendering of the rest state (formatted). Falls back to value / placeholder. |
editor | The edit control (defaults to hy-text-input, or a bare input under emphasis="plain"); a slotted editor wins over both. |
CSS Parts
| Part | Description |
|---|---|
display | The display-view wrapper. |
edit | The edit-view wrapper. |
helper-text | The helper line under the field. |
error-message | The error line under the field. |
warning-message | The warning line under the field. |
message-icon | The 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.
| Property | Default | Description |
|---|---|---|
--hy-inline-edit-foreground-danger-rest | var(--hy-foreground-danger-rest) | Error line and error icon color. |
--hy-inline-edit-foreground-warning-rest | var(--hy-foreground-warning-rest) | Warning line, warning ring and warning icon color. |
--hy-inline-edit-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Helper line color. |
--hy-inline-edit-message-gap-layout-2xs | var(--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) |