Scenario

Caret jumps to end when deleting character next to non-editable element

When deleting the last character before a non-editable "pill" or tag element (contenteditable="false") in a contenteditable div in Chrome, the caret (cursor) jumps to the end of the entire contenteditable div instead of staying adjacent to the remaining content.

selection
Scenario ID
scenario-caret-jump-non-editable

Details

When deleting characters adjacent to non-editable elements (e.g., tags, pills with contenteditable="false") in a contenteditable div, the caret unexpectedly jumps to the end of the entire editor in Chrome.

Problem Description

This issue occurs when:

  1. A contenteditable div contains non-editable elements (e.g., <span contenteditable="false">)
  2. User types text before the non-editable element
  3. User deletes the last character before the non-editable element

Expected Behavior

  • Caret should remain adjacent to the non-editable element
  • User should be able to continue typing immediately after deletion

Actual Behavior (Chrome Bug)

  • Caret jumps to end: Caret moves to the end of the entire contenteditable div
  • User must click: User has to manually click back to correct position to continue typing
  • Poor UX: Makes continuous typing impossible after deletion near non-editable elements

Affected Browsers

  • Chrome (all versions) - Issue confirmed
  • Firefox - Does NOT exhibit this behavior (works correctly)
  • Safari - May have similar issues but less consistent

Root Cause

Chrome’s caret positioning algorithm appears to incorrectly calculate where the caret should go when:

  1. A non-editable element (contenteditable="false") is in the DOM
  2. Text before the non-editable element is deleted
  3. Chrome recalculates caret position and mistakenly places it at the end instead of adjacent to the remaining non-editable element

Workarounds

  1. Set display: inline-block on contenteditable:

    .contenteditable {
      display: inline-block;
    }
  2. Add zero-width space after non-editable elements:

    // Insert ZWSP after non-editable element
    const zwsp = document.createTextNode('\u200B');
    nonEditableElement.parentNode.insertBefore(zwsp, nonEditableElement.nextSibling);
  3. Use empty span as placeholder:

    // Add empty span after non-editable element
    const placeholder = document.createElement('span');
    placeholder.innerHTML = '&nbsp;';
    nonEditableElement.parentNode.insertBefore(placeholder, nonEditableElement.nextSibling);
  4. Wrap editable content in separate div:

    <div contenteditable="false">
      <div contenteditable="true">editable content</div>
    </div>
  5. Programmatically restore caret position after deletion:

    editor.addEventListener('input', (e) => {
      const selection = window.getSelection();
      // Check if caret jumped
      if (caretJumped) {
        // Restore position
        const range = document.createRange();
        range.setStartBefore(nonEditableElement);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    });

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-0266-caret-jump-non-editable-firefox-en macOS 13+ Desktop (Mac) Any Firefox 120+ English (QWERTY) confirmed

Cases

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

Related Scenarios

Other scenarios that share similar tags or category.

Tags: caret, cursor, chrome

Text caret is invisible on position:relative elements

When editing content inside an element with `position:relative`, the text caret (cursor) is completely invisible. Text can be typed and appears in the editor, but there's no visual feedback of where the insertion point is located.

2 cases
Tags: caret, cursor, chrome

Typing certain characters makes cursor jump on Chrome Mobile

On Chrome Mobile for Android, typing certain punctuation characters (commas, colons, semicolons, quotes, etc.) in the middle of a word causes the cursor to jump to the end of the word instead of staying at the insertion point.

2 cases
Tags: caret

Browser zoom causes caret and selection positioning issues

When the browser is zoomed (or content is scaled via CSS transforms), caret position and text selection in contenteditable elements can become inaccurate. Clicking at a certain position places the caret elsewhere, and selection highlights may not match the visual selection.

1 case
Tags: cursor

Cursor disappears with contenteditable="false" elements

When contenteditable='false' elements are placed inside a contenteditable container, the cursor may disappear or become invisible in certain browsers, making it difficult for users to determine the text insertion point.

0 cases

Comments & Discussion

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