Inline edit text
Category: Forms · Tags: inline-edit, editable, form, settings, focus
How to use it
Use for one short value that people change occasionally while reading a page, such as a display name or a project title. For several related fields, or values that need review before saving, use a normal form with one Save button instead. Rename the label, the hidden "display name" words and the ids, and send the saved value to your server in the submit handler.
Accessibility
The value is plain text with a native Edit <button> whose accessible name is "Edit display name" (visually hidden words complete it). Pressing it swaps in a real <form> with a <label for> on the input and native Save and Cancel buttons; focus moves into the input, Escape or Cancel restores the old value, and focus returns to the Edit button afterwards. Saving an empty value sets aria-invalid with a linked error, and the result is announced through role="status". Text is #0f172a or #475569 on white (17.85:1, 7.58:1), Save is white on #1d4ed8 (6.70:1, 8.72:1 hovered), errors #b91c1c on white (6.47:1), with a 2px #1d4ed8 focus outline.
Source (copy-ready)
<div class="dcl-inline-edit">
<div class="dcl-inline-edit__view">
<span class="dcl-inline-edit__label">Display name</span>
<span class="dcl-inline-edit__value">Ana Ruiz</span>
<button type="button" class="dcl-inline-edit__edit">Edit<span class="dcl-inline-edit__sr"> display name</span></button>
</div>
<form class="dcl-inline-edit__form" hidden>
<label class="dcl-inline-edit__label" for="dcl-inline-edit-input">Display name</label>
<input class="dcl-inline-edit__input" type="text" id="dcl-inline-edit-input" name="display-name" autocomplete="nickname" aria-describedby="dcl-inline-edit-error">
<div class="dcl-inline-edit__actions">
<button type="submit" class="dcl-inline-edit__save">Save</button>
<button type="button" class="dcl-inline-edit__cancel">Cancel</button>
</div>
<p class="dcl-inline-edit__error" id="dcl-inline-edit-error" role="alert"></p>
</form>
<p class="dcl-inline-edit__status" role="status"></p>
</div>
<style>
.dcl-inline-edit { max-width: 32rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-inline-edit__view { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; }
.dcl-inline-edit__view[hidden] { display: none; }
.dcl-inline-edit__label { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-inline-edit__value { min-width: 0; overflow-wrap: anywhere; color: #334155; background-color: #ffffff; }
.dcl-inline-edit__form { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; }
.dcl-inline-edit__form[hidden] { display: none; }
.dcl-inline-edit__input { box-sizing: border-box; flex: 1 1 12rem; min-width: 0; max-width: 100%; padding: 0.45rem 0.6rem; border: 1px solid #64748b; border-radius: 6px; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-inline-edit__input[aria-invalid="true"] { border: 2px solid #b91c1c; }
.dcl-inline-edit__input:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 1px; }
.dcl-inline-edit__actions { display: flex; gap: 0.5rem; }
.dcl-inline-edit__edit, .dcl-inline-edit__cancel { padding: 0.35rem 0.8rem; border: 1px solid #1d4ed8; border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer; color: #1d4ed8; background-color: #ffffff; }
.dcl-inline-edit__edit:hover, .dcl-inline-edit__cancel:hover { color: #1e40af; background-color: #eff6ff; }
.dcl-inline-edit__save { padding: 0.35rem 0.8rem; border: 1px solid #1d4ed8; border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer; color: #ffffff; background-color: #1d4ed8; }
.dcl-inline-edit__save:hover { color: #ffffff; background-color: #1e40af; }
.dcl-inline-edit__edit:focus-visible, .dcl-inline-edit__cancel:focus-visible, .dcl-inline-edit__save:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-inline-edit__error { flex-basis: 100%; margin: 0; font-size: 0.9rem; color: #b91c1c; background-color: #ffffff; }
.dcl-inline-edit__status { margin: 0.5rem 0 0; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-inline-edit__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
</style>
<script>
(function () {
document.querySelectorAll(".dcl-inline-edit").forEach(function (root) {
if (root.dataset.dclBound) { return; }
root.dataset.dclBound = "true";
var view = root.querySelector(".dcl-inline-edit__view");
var value = root.querySelector(".dcl-inline-edit__value");
var edit = root.querySelector(".dcl-inline-edit__edit");
var form = root.querySelector(".dcl-inline-edit__form");
var input = root.querySelector(".dcl-inline-edit__input");
var cancel = root.querySelector(".dcl-inline-edit__cancel");
var error = root.querySelector(".dcl-inline-edit__error");
var status = root.querySelector(".dcl-inline-edit__status");
if (!view || !value || !edit || !form || !input) { return; }
function say(node, text) { if (node) { node.textContent = text; } }
function close(message) {
form.hidden = true;
view.hidden = false;
input.removeAttribute("aria-invalid");
say(error, "");
say(status, message);
edit.focus();
}
edit.addEventListener("click", function () {
input.value = value.textContent;
say(status, "");
view.hidden = true;
form.hidden = false;
input.focus();
input.select();
});
if (cancel) { cancel.addEventListener("click", function () { close("No changes made."); }); }
form.addEventListener("keydown", function (event) {
if (event.key === "Escape") { event.preventDefault(); close("No changes made."); }
});
form.addEventListener("submit", function (event) {
event.preventDefault();
var next = input.value.trim();
if (!next) {
input.setAttribute("aria-invalid", "true");
say(error, "Enter a display name.");
input.focus();
return;
}
value.textContent = next;
close("Display name saved.");
});
});
})();
</script>
Was this useful? Send feedback.