Scenario

Deleted inline elements recreated when typing in contenteditable

After deleting an empty or inline element (e.g. span, b) inside contenteditable, typing causes the browser to recreate the deleted element, leading to unpredictable DOM and editor state.

formatting
Scenario ID
scenario-inline-element-recreation-after-delete

Details

Problem Overview

In Chromium (and with varying behavior in other engines), when the user deletes an empty inline element such as <span> or <b> inside a contenteditable region and then types, the browser may automatically recreate that deleted inline element. This “smart” DOM modification contradicts the expectation that the editor controls the DOM; it causes state divergence in framework-based editors and complicates normalization logic.

Observed Behavior

  • Trigger: User deletes an empty inline element (e.g. Backspace/Delete removes a <span> or <b> with no text), then types a character.
  • Result: The deleted inline wrapper reappears around the newly typed content or in an unexpected place.
  • Scope: Confirmed in Chromium; behavior may differ in Safari and Firefox. Adding any text content to the span before deletion, or using non-inline display on the span, can avoid or change the behavior.

Example DOM before/after:

<!-- Before: user deletes the empty <span> and types "x" -->
<div contenteditable="true">hello <span></span> world</div>

<!-- Chromium may produce -->
<div contenteditable="true">hello <span>x</span> world</div>

Impact

  • State corruption: React/Vue/Svelte reconciliation can desync when the DOM is modified by the browser without going through the framework.
  • Undo/redo: Browser re-creation can conflict with custom history stacks.
  • Predictability: Editors cannot assume that “delete then type” leaves the DOM in a deterministic shape.

Browser Comparison

  • Chrome (Blink): Recreates deleted empty inline elements when typing; linked to execCommand/editing spec legacy behavior.
  • Safari (WebKit): May not always recreate; behavior can depend on structure.
  • Firefox (Gecko): Different behavior; less aggressive recreation in many cases.

Solutions

  1. Normalize after input: On input or beforeinput, walk the DOM and remove or merge redundant inline wrappers that the editor did not create.
  2. Avoid empty inline nodes: When generating HTML, avoid leaving empty <span> or <b> etc.; use text nodes or placeholders (e.g. \u200B) so the node is not “empty” from the engine’s perspective.
  3. Intercept deletion: Use beforeinput with inputType deleteContentBackward/deleteContentForward to apply your own DOM change and preventDefault() so the browser does not perform its default delete (and subsequent recreation) when feasible.

Example normalization (remove empty inlines):

editor.addEventListener('input', () => {
  const emptyInlines = editor.querySelectorAll('span:empty, b:empty, i:empty');
  emptyInlines.forEach(el => {
    const parent = el.parentNode;
    while (el.firstChild) parent.insertBefore(el.firstChild, el);
    el.remove();
  });
});

Best Practices

  • Do not rely on the browser to leave the DOM unchanged after delete-then-type.
  • Prefer a single source of truth (e.g. framework state) and normalize DOM on input to match.
  • Track official fixes: W3C editing and Chromium may change this behavior in future.
  • ce-0582 – Chromium empty span recreated after delete and type

References

Scenario flow

Visual view of how this scenario connects to its concrete cases and environments. Nodes can be dragged and clicked.

React Flow mini map

Variants

Each row is a concrete case for this scenario, with a dedicated document and playground.

Case OS Device Browser Keyboard Status
ce-0582 Windows 11 Desktop Any Chrome 124.0 US QWERTY draft

Cases

Open a case to see the detailed description and its dedicated playground.

Related Scenarios

Other scenarios that share similar tags or category.

Tags: dom

Empty elements accumulate in DOM during editing

During editing operations, empty elements (empty paragraphs, divs, spans with no content) accumulate in the DOM. These elements can cause layout issues, make the HTML bloated, and create unexpected behavior. Browsers handle empty element cleanup inconsistently.

5 cases
Tags: execCommand

execCommand is deprecated but still widely used for formatting

The document.execCommand() API, which is commonly used to apply formatting (bold, italic, etc.) in contenteditable regions, has been deprecated. However, there is no complete replacement, and many implementations still rely on it. This creates uncertainty about future browser support.

1 case
Tags: delete

Image deletion behavior varies across browsers

Deleting images from contenteditable elements behaves differently across browsers. Some browsers delete the image cleanly, while others may leave empty elements, break the DOM structure, or require multiple delete operations.

3 cases
Tags: dom

insertHTML breaks DOM structure and formatting

When using document.execCommand('insertHTML', ...) to insert HTML content into a contenteditable region, the DOM structure may be broken or reformatted unexpectedly. Nested elements may be flattened or reorganized.

1 case
Tags: delete

List item deletion behavior varies across browsers

When pressing Backspace or Delete at the beginning or end of a list item, the behavior varies significantly across browsers. Some browsers delete the list item and merge with adjacent content, while others may delete the entire list or create unexpected DOM structures.

3 cases

Comments & Discussion

Have questions, suggestions, or want to share your experience? Join the discussion below.