Case ce-0582 · Scenario scenario-inline-element-recreation-after-delete

Chromium recreates empty span after delete when user types

OS: Windows 11 Device: Desktop Any Browser: Chrome 124.0 Keyboard: US QWERTY Status: draft
chromium inline span delete dom formatting

Phenomenon

In Chromium, when the user deletes an empty inline element (e.g. <span>, <b>, <i>) inside a contenteditable and then types a character, the editing engine recreates the deleted inline element and wraps the newly typed character. This comes from legacy execCommand/editing spec behavior (“recording and restoring overrides”). The input event fires after the DOM has already been modified by the browser, so the editor sees a DOM that no longer matches the state it had before the delete (e.g. no span). No beforeinput with inputType clearly describes “recreate deleted inline”; the visible effect is a new wrapper around the typed character.

Reproduction Steps

  1. Create a contenteditable div containing: hello <span></span> world (empty span between two text runs).
  2. Place the caret immediately after the space that follows “hello” (i.e. before the empty span).
  3. Press Backspace once so the empty span is removed (or place caret after the span and press Delete).
  4. Type a single character (e.g. “x”).
  5. Inspect the DOM: a <span>x</span> (or similar) appears instead of a plain text node “x”.

Observed Behavior

  • Event sequence: keydown (Backspace) → default delete removes empty span → input. Then keydown (“x”) → default insert → beforeinput (e.g. insertText) → input. After input, the DOM contains a new inline wrapper around the typed character.
  • Consistency: Adding any character or space inside the span before deleting it (so the span is not “empty”) often avoids recreation. Using display: block (or other non-inline) on the span can also change or avoid the behavior.
  • Other engines: Safari and Firefox may not recreate the span in the same way; behavior is Chromium-specific in practice.

Expected Behavior

Per predictable editing semantics, deleting an inline element and then typing should result in the typed character being inserted as a normal text node (or merged into an adjacent text node). The browser should not re-invent a previously deleted inline wrapper. The Input Events spec does not define “recreate deleted inline” as a standard action.

Impact

  • State corruption: React/Vue/Svelte treat the DOM as derived from their state; unexpected insertion of a <span> breaks reconciliation and can cause duplicate or wrong content.
  • Undo/redo: Custom history that records “delete span” then “insert text” will not match the final DOM (which has a new span).
  • Serialization: HTML export may contain extra formatting (e.g. <span>x</span>) that the user did not intend.

Browser Comparison

  • Chrome (Blink): Recreates empty inline on type; confirmed in 124.x.
  • Safari (WebKit): May not recreate in the same scenario; structure-dependent.
  • Firefox (Gecko): Typically does not recreate the deleted empty inline in the same way.

Solutions

  1. Normalize on input: In the input handler, walk the editable root and remove or merge redundant inline elements (e.g. span:empty, or unwrap single-text-node spans that the editor did not create).
  2. Avoid empty inlines: When building content, avoid leaving empty <span>/<b>/<i> nodes; use a zero-width space (\u200B) or ensure the node has content so the engine does not treat it as “empty” for this path.
  3. beforeinput + preventDefault: For insertText/insertCompositionText, you can preventDefault and apply your own DOM update so the browser does not run its insert (and thus does not recreate the inline). This requires correct getTargetRanges() usage and caret restoration.

References

Step 1: Empty span in content
hello world
Contenteditable contains an empty span between two text nodes.
Step 2: User deletes empty span (Backspace)
hello world
User places caret after space before world, Backspace removes the empty span; DOM may show collapsed space.
vs
✅ Expected
hello x world
Expected: No new inline wrapper; typed character is a plain text node.

Playground for this case

Use the reported environment as a reference and record what happens in your environment while interacting with the editable area.

Reported environment
OS: Windows 11
Device: Desktop Any
Browser: Chrome 124.0
Keyboard: US QWERTY
Your environment
Sample HTML:
Event log
Use this log together with the case description when filing or updating an issue.
0 events
Interact with the editable area to see events here.

Comments & Discussion

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