Editable
stimeo--editable
Inline edit: click or F2 to edit text in place, Enter to save, Escape to cancel.
The stimeo--editable controller turns a display element into an inline editor. Activating the display (Enter/Space on the button, or F2) enters edit mode, focuses the input and selects its text. Enter (or Ctrl+Enter / Cmd+Enter for a multiline textarea) saves and Escape cancels; both return focus to the display element. With submitOnBlur (the default) losing focus also saves, honoring wherever focus moved. Saving updates the display text and dispatches stimeo--editable:change with { value, previous } only when the value actually changed; cancelling dispatches stimeo--editable:cancel. The look is your CSS, keyed off data-mode.
Keyboard
| Key | Action |
|---|---|
| Enter / Space / F2 | Enter edit mode from the display element. |
| Enter | Save (single-line); inserts a newline in a textarea. |
| Ctrl+Enter / Cmd+Enter | Save when editing a multiline textarea. |
| Esc | Discard changes and return to display mode. |
<%# Markup for the editable (inline editing) demo.
Click / Enter / F2 on the display element (a button) enters edit mode, focusing the
input and selecting all. The library handles mode switching (data-mode), focus /
select-all, and the commit event; the look lives in demo.css.
Two variants are shown:
1. Single-line <input>: Enter saves, Escape cancels.
2. Multiline <textarea>: Enter inserts a newline, Ctrl+Enter saves, Escape cancels.
submitOnBlur=true also saves on blur for both. %>
<div class="editable-demo">
<%# 1. Single-line inline edit. %>
<div class="editable" data-controller="stimeo--editable"
data-stimeo--editable-submit-on-blur-value="true">
<span class="editable__hint"><%= t("components.editable.demo.hint") %></span>
<button
type="button"
class="editable__display"
aria-label="<%= t("components.editable.demo.aria_label") %>"
data-stimeo--editable-target="display"
data-action="click->stimeo--editable#edit keydown->stimeo--editable#onDisplayKeydown">
<%= t("components.editable.demo.value") %>
</button>
<input
type="text"
class="editable__input"
hidden
aria-label="<%= t("components.editable.demo.aria_label") %>"
data-stimeo--editable-target="input"
data-action="keydown->stimeo--editable#onKeydown blur->stimeo--editable#onBlur" />
</div>
<%# 2. Multiline inline edit. The controller detects the <textarea> and treats Enter as a
newline, so saving is Ctrl+Enter (or blur). The display uses white-space: pre-line so
saved line breaks render. %>
<div class="editable" data-controller="stimeo--editable"
data-stimeo--editable-submit-on-blur-value="true">
<span class="editable__hint"><%= t("components.editable.demo.multiline.hint") %></span>
<button
type="button"
class="editable__display editable__display--multiline"
aria-label="<%= t("components.editable.demo.multiline.aria_label") %>"
data-stimeo--editable-target="display"
data-action="click->stimeo--editable#edit
keydown->stimeo--editable#onDisplayKeydown"><%= t(
"components.editable.demo.multiline.value"
) %></button>
<textarea
class="editable__input editable__input--multiline"
hidden
rows="3"
aria-label="<%= t("components.editable.demo.multiline.aria_label") %>"
data-stimeo--editable-target="input"
data-action="keydown->stimeo--editable#onKeydown blur->stimeo--editable#onBlur"></textarea>
</div>
</div>
/*
* Presentation-only styles for the editable demo.
* The library switches modes via data-mode and each element's hidden attribute.
* Here we give the display element an "editable-looking" appearance and style the
* input while editing.
*/
.editable-demo {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.editable {
display: inline-flex;
flex-direction: column;
gap: 0.35rem;
min-width: 16rem;
}
.editable__hint {
font-size: 0.8125rem;
color: var(--color-text-muted);
}
.editable__display {
text-align: left;
padding: 0.4rem 0.6rem;
border: 1px dashed transparent;
border-radius: 0.375rem;
background: transparent;
color: var(--fg, var(--color-text));
font: inherit;
font-weight: 600;
cursor: text;
}
.editable__display:hover {
border-color: var(--border-strong);
background: var(--surface-subtle);
}
/* Multiline display: render saved line breaks (textContent keeps the newlines). */
.editable__display--multiline {
white-space: pre-line;
}
.editable__input {
padding: 0.4rem 0.6rem;
border: 1px solid var(--accent, var(--color-primary));
border-radius: 0.375rem;
font: inherit;
color: var(--fg, var(--color-text));
}
.editable__input--multiline {
resize: vertical;
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--editable"
Targets
| Name | Description | Attribute |
|---|---|---|
display
required
|
The element (typically a button) shown in display mode and activated to enter edit mode. | data-stimeo--editable-target="display" |
input
required
|
The input or textarea shown in edit mode that holds the editable value. | data-stimeo--editable-target="input" |
Values
| Name | Description | Attribute |
|---|---|---|
submitOnBlur
|
When true (default), losing focus while editing saves; when false, editing is kept. | data-stimeo--editable-submit-on-blur-value |
Actions
| Name | Description | Action |
|---|---|---|
edit
|
Enters edit mode: seeds the input from the display text, focuses it, and selects its content. | stimeo--editable#edit |
onBlur
|
On blur while editing, saves when submitOnBlur is set; otherwise does nothing. |
stimeo--editable#onBlur |
onDisplayKeydown
|
Handles F2 on the display element as an extra entry point into edit mode. | stimeo--editable#onDisplayKeydown |
onKeydown
|
Commits on Enter (Ctrl/Cmd+Enter when multiline) and cancels on Escape while editing. | stimeo--editable#onKeydown |
Events
| Name | Description | Event |
|---|---|---|
cancel
|
Dispatched when editing is cancelled via Escape, discarding the edits. | stimeo--editable:cancel |
change
|
Dispatched after a successful save only when the value changed, with { value, previous } in detail. |
stimeo--editable:change |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
data-mode |
Controller element | "display" or "editing". |
hidden |
Display / Input | Present on whichever element is not active for the current mode. |