Accessibility pattern · Communication

Inline edit

Each value is plain text with an Edit button named for it; pressing it swaps in a labelled field with Save and Cancel, and focus goes into the field. Enter saves and Escape cancels, and either way focus returns to the Edit button, with a polite status confirming what was saved.

WCAG criteria
7
Keyboard rules
5
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Project settings

Kesar Tea · Monsoon range

Project name

Monsoon launch

Starts

Due date

Description

Landing page, emails and launch-day offers for the monsoon tea range.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves from one Edit button to the next; while a field is open, through the field, Save and Cancel.
Enter or SpaceOn an Edit button, swaps the value for a field and moves focus into it.
EnterIn the name or the date, saves and returns focus to the Edit button. In the description, starts a new line.
CtrlEnterIn the description, saves it.
EscapeCancels: puts the old value back and returns focus to the Edit button.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Tab reaches an Edit buttonEdit project name, button
Enter opens the fieldProject name, edit text, Monsoon launch, selected
Enter saves a new nameEdit project name, button. Project name saved
Enter with the field emptyEnter a project name.
Escape cancelsEdit project name, button

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<section class="ap-inline-edit" data-ap-inline-edit aria-labelledby="ie-name">
  <div class="ap-inline-edit__top">
    <h3 class="ap-inline-edit__name" id="ie-name">Project settings</h3>
    <p class="ap-inline-edit__sub">Kesar Tea · Monsoon range</p>
  </div>
  <dl class="ap-inline-edit__list">
    <div class="ap-inline-edit__row" data-ap-ie-row="project">
      <dt class="ap-inline-edit__key" id="ie-project-k">Project name</dt>
      <dd class="ap-inline-edit__cell">
        <div class="ap-inline-edit__view">
          <p class="ap-inline-edit__value" data-ap-ie-value>Monsoon launch</p>
          <button type="button" class="ap-inline-edit__edit" data-ap-ie-edit aria-label="Edit project name"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg><span>Edit</span></button>
        </div>
        <form class="ap-inline-edit__form" data-ap-ie-form novalidate hidden>
          <input class="ap-input" id="ie-project" aria-labelledby="ie-project-k" data-ap-ie-input name="project" value="Monsoon launch" autocomplete="off" />
          <p class="ap-error" id="ie-project-error" hidden><svg class="ap-inline-edit__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5"/><path d="M12 16.5h.01"/></svg><span></span></p>
          <div class="ap-inline-edit__btns">
            <button type="submit" class="ap-btn ap-btn--primary">Save</button>
            <button type="button" class="ap-btn" data-ap-ie-cancel>Cancel</button>
          </div>
        </form>
      </dd>
    </div>
    <div class="ap-inline-edit__row ap-inline-edit__row--fixed">
      <dt class="ap-inline-edit__key">Starts</dt>
      <dd class="ap-inline-edit__cell"><p class="ap-inline-edit__value"><time datetime="2026-10-01">1 October 2026</time></p></dd>
    </div>
    <div class="ap-inline-edit__row" data-ap-ie-row="due">
      <dt class="ap-inline-edit__key" id="ie-due-k">Due date</dt>
      <dd class="ap-inline-edit__cell">
        <div class="ap-inline-edit__view">
          <p class="ap-inline-edit__value" data-ap-ie-value><time datetime="2026-11-14">14 November 2026</time></p>
          <button type="button" class="ap-inline-edit__edit" data-ap-ie-edit aria-label="Edit due date"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg><span>Edit</span></button>
        </div>
        <form class="ap-inline-edit__form" data-ap-ie-form novalidate hidden>
          <input class="ap-input ap-inline-edit__date" id="ie-due" aria-labelledby="ie-due-k" data-ap-ie-input name="due" type="date" value="2026-11-14" min="2026-10-02" />
          <p class="ap-error" id="ie-due-error" hidden><svg class="ap-inline-edit__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5"/><path d="M12 16.5h.01"/></svg><span></span></p>
          <div class="ap-inline-edit__btns">
            <button type="submit" class="ap-btn ap-btn--primary">Save</button>
            <button type="button" class="ap-btn" data-ap-ie-cancel>Cancel</button>
          </div>
        </form>
      </dd>
    </div>
    <div class="ap-inline-edit__row" data-ap-ie-row="desc">
      <dt class="ap-inline-edit__key" id="ie-desc-k">Description</dt>
      <dd class="ap-inline-edit__cell">
        <div class="ap-inline-edit__view">
          <p class="ap-inline-edit__value" data-ap-ie-value>Landing page, emails and launch-day offers for the monsoon tea range.</p>
          <button type="button" class="ap-inline-edit__edit" data-ap-ie-edit aria-label="Edit description"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg><span>Edit</span></button>
        </div>
        <form class="ap-inline-edit__form" data-ap-ie-form novalidate hidden>
          <textarea class="ap-input" id="ie-desc" aria-labelledby="ie-desc-k" aria-describedby="ie-desc-hint" data-ap-ie-input name="desc" rows="3" aria-keyshortcuts="Control+Enter">Landing page, emails and launch-day offers for the monsoon tea range.</textarea>
          <p class="ap-hint" id="ie-desc-hint">Ctrl + Enter saves. Up to 200 characters.</p>
          <p class="ap-error" id="ie-desc-error" hidden><svg class="ap-inline-edit__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5"/><path d="M12 16.5h.01"/></svg><span></span></p>
          <div class="ap-inline-edit__btns">
            <button type="submit" class="ap-btn ap-btn--primary">Save</button>
            <button type="button" class="ap-btn" data-ap-ie-cancel>Cancel</button>
          </div>
        </form>
      </dd>
    </div>
  </dl>
  <div class="ap-inline-edit__foot" data-ap-ie-foot hidden>
    <button type="button" class="ap-btn ap-btn--primary" data-ap-ie-saveall>Save changes</button>
  </div>
  <p class="ap-inline-edit__status" role="status"></p>
  <p class="ap-inline-edit__vh" aria-live="polite" data-ap-ie-say></p>
</section>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    Each value and its label are a term and description in a list; the field takes its name from the same label with aria-labelledby.

  • 2.1.1 Keyboard Level A

    Editing starts from a real button, and saving and cancelling both have keys as well as buttons.

  • 2.4.3 Focus Order Level A

    Opening a field moves focus into it; saving or cancelling puts it back on the Edit button it came from.

  • 2.5.3 Label in Name Level A

    Each Edit button's name starts with the word it shows, Edit, and says which value it edits.

  • 3.3.1 Error Identification Level A

    An empty name or an impossible date is not saved; the error is shown under the field, tied to it and announced.

  • 4.1.2 Name, Role, Value Level A

    Values are read as text, not as fields pretending to be text; the field appears with its real role only while editing.

  • 4.1.3 Status Messages Level AA

    A polite status says what was saved, without moving focus.

Usage

When to use it

Use it

  • Settings and records people mostly read and occasionally change, one value at a time.
  • Dense pages where a full form for every value would be noise.

Use something else

  • Forms people fill in from scratch: show every field as a field, as the Always editable option does.
  • Values that must change together, like an address: edit them in one form with one Save.
  • Values people change often: an always-visible field saves a click every time.

Common failures

How it usually goes wrong

  • A contenteditable span that looks like text

    It has no role, no label and no visible edge, so screen readers announce plain text, keyboard users never know they can type, and there is no Save, Cancel or error. A real button and a real field fix all of that.

  • Click on the text itself to edit

    A clickable span or a div is not in the Tab order and is announced as text. The Edit button here is a button, named for the value it edits.

  • Saving on blur

    Saving as focus leaves means a stray Tab commits half-typed text, and Escape has nothing to undo. Here only Enter or Save saves.

  • Focus lost when the field closes

    If the field disappears with focus in it, keyboard users are sent to the top of the page. Focus returns to the Edit button every time.

  • Edit buttons that all say Edit

    Three buttons called Edit cannot be told apart in a list of buttons. Each one here is named Edit project name, Edit due date and so on.

  • An error nobody hears

    Turning the border red on a field that already has focus says nothing to a screen reader. The message is tied to the field and also announced.

Notes

Building it

  • The field takes its name from the dt with aria-labelledby, so the visible label and the accessible name are always the same words.
  • Keep the saved value in script, not in the field, so Cancel can put it back after any number of edits.
  • The Always editable option shows the same values as ordinary fields with one Save changes button; compare the two by keyboard to see the cost of each.
  • Enter adds a line in the description, which is why it saves with Ctrl+Enter and says so in its hint.

Sources: WAI-ARIA Authoring Practices: Button · MDN: the contenteditable attribute

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction