Scenario

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.

selection
Scenario ID
scenario-browser-zoom

Details

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.

Observed Behavior

  • Caret position mismatch: Clicking at a position shows caret elsewhere
  • Selection inaccuracy: Selection highlights wrong text
  • Caret disappearing: In Firefox and IE when transform: scale() is used, caret becomes invisible
  • Caret jumps: Caret misplaces when navigating around contenteditable="false" elements with zoom
  • Pixel rounding errors: Sub-pixel rendering causes coordinate calculation issues

Browser Comparison

  • Firefox: Most affected, especially with CSS transforms
  • Chrome: Issues with zoom and non-editable elements (fixed in v106+)
  • Safari: Similar issues with transforms
  • Edge: Similar to Chrome
  • IE: Historical issues with zoom and caret positioning

Impact

  • Poor user experience: Users cannot accurately place cursor or select text
  • Accessibility issues: Makes editing difficult for users who need zoom
  • Visual mismatch: What users see doesn’t match actual selection
  • Editing frustration: Continuous editing becomes difficult

Workarounds

1. Use display: inline-block

Helps Chrome with caret placement:

[contenteditable="true"] {
  display: inline-block;
}

2. Add Visible BR When Empty

Prevents Firefox caret invisibility:

if (editableElement.innerHTML.trim() === '') {
  editableElement.innerHTML = '<br>';
}

3. Insert Zero-Width Spaces

Around contenteditable="false" elements:

function insertZWS(element) {
  const zws = document.createTextNode('\u200B');
  element.parentNode.insertBefore(zws, element.nextSibling);
}

4. Avoid CSS transform: scale

Use font-size or layout adjustments instead:

/* Instead of: */
.editor {
  transform: scale(1.5);
}

/* Use: */
.editor {
  font-size: 150%;
}

5. Use window.visualViewport API

For custom UI calculations:

function getViewportAdjustedRect(element) {
  const rect = element.getBoundingClientRect();
  const viewport = window.visualViewport;
  return {
    top: rect.top - viewport.offsetTop,
    left: rect.left - viewport.offsetLeft,
    width: rect.width / viewport.scale,
    height: rect.height / viewport.scale
  };
}

6. Consistent Line-Height

Prevents dead-space clicking issues:

[contenteditable="true"] {
  line-height: 1.5;
  font-size: 16px;
}

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-0563-browser-zoom-caret-positioning Any Any Desktop or Laptop Any Firefox Latest US 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: caret

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

Comments & Discussion

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