Scenario

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.

selection
Scenario ID
scenario-caret-invisible-relative

Details

When editing content inside an element that has position:relative CSS property, the text caret (cursor) is invisible and provides no visual feedback of the insertion point.

Problem Description

This issue occurs when:

  1. A contenteditable element is nested inside or has position:relative styling
  2. User focuses on the editable area
  3. No text caret is visible

Expected Behavior

  • Text caret should be visible at the insertion point
  • User should see blinking line or other caret indicator
  • Typing should provide clear visual feedback

Actual Behavior (WebKit Bug)

  • No visible caret: Caret is completely invisible
  • Text appears: Typed text appears in the editor
  • No visual feedback: User cannot see where next character will be inserted
  • Editing is difficult: User has to guess where typing will occur

Affected Browsers

  • Safari (15.1, 15.5, 17+) on macOS - Issue confirmed
  • Safari (iOS) - Issue confirmed
  • Chrome (on macOS) - Issue confirmed
  • Firefox (95+) on macOS - Issue confirmed

Root Cause

WebKit’s text caret rendering fails when:

  1. The element or its ancestor has position:relative
  2. Contenteditable is nested inside such element
  3. The caret calculation and rendering doesn’t account for relative positioning

The position:relative property creates a new positioning context that breaks the caret rendering algorithm in WebKit-based browsers.

Workarounds

  1. Remove position:relative:

    .editable-container {
      position: static; /* or remove position property */
    }
  2. Use position:static instead:

    .editable-container {
      position: static;
    }
  3. Move position:relative to ancestor element:

    /* Make wrapper relative, keep editable static */
    .wrapper {
      position: relative;
    }
    .editable {
      position: static;
    }
  4. Use caret-color property (may help but not complete fix):

    [contenteditable] {
      caret-color: black; /* May work in some browsers */
    }
  5. Provide custom caret element:

    // Create custom caret indicator
    const caret = document.createElement('span');
    caret.className = 'custom-caret';
    caret.style.cssText = `
      position: absolute;
      width: 2px;
      height: 20px;
      background: black;
      animation: blink 1s infinite;
    `;

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-0272-caret-invisible-relative-chrome-macos-en macOS 13+ Desktop (Mac) Any Chrome 120+ English (QWERTY) draft
ce-0273-caret-invisible-relative-firefox-windows-en Windows 10/11 Desktop Any Firefox 120+ English (QWERTY) draft

Browser compatibility

This matrix shows which browser and OS combinations have documented cases for this scenario. Click on a cell to view the specific case.

Confirmed
Draft
No case documented

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

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, cursor, chrome

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.

1 case
Tags: cursor, webkit, safari

selection.addRange not working correctly in Safari

When setting cursor position using `selection.addRange()` in a contenteditable element, it works correctly in Chrome and Firefox but fails in Safari. The selection "pops out" of intended marker element and moves to the next sibling's text node instead of staying within the marker.

3 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.