# Caret flickers across a mark view with \`contenteditable=false\` child

**URL:** <https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075>\
**Category:** Uncategorized\
**Created:** [September 7, 2026, 5:17am UTC](https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075 "2026-09-07T05:17:56Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![ocavue](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/ocavue/32/2239_2.png) [@ocavue](https://discuss.prosemirror.net/u/ocavue)\
**Post date:** [September 7, 2026, 5:17am UTC](https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075/1 "2026-09-07T05:17:56Z")

</div>

Online demo: [ProseMirror mark view selection repro](https://issueset.github.io/repro-pm-markview-selection-ios/)

Source code: [GitHub - issueset/repro-pm-markview-selection-ios · GitHub](https://github.com/issueset/repro-pm-markview-selection-ios/)

A `mention` mark view renders a non-editable label followed by the editable source text:

```auto
<span class="mention" data-dom>
  <span class="label" contenteditable="false">🅰️TeamA</span>
  <span class="source" data-content-dom>🅱️@Alice</span>
</span>

```

🅰 and 🅱 are different DOM positions, but they should be treated as the same ProseMirror document position.

However, [`isEquivalentPosition`](https://code.haverbeke.berlin/prosemirror/prosemirror-view/src/tag/1.42.3/src/dom.ts#L39) treats them as different ProseMirror positions because its scan stops at the `contenteditable=false` element. So whenever the browser places the text caret on 🅰, `prosemirror-view` moves it to 🅱, causing the caret to flicker.

More specifically, this causes the following behavior:

1. On macOS Chrome, I cannot press `ArrowRight` to move the caret across the mark view.

2. On iOS Safari, I cannot long-press the spacebar to move the caret across the mark view.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)\
**Post date:** [September 7, 2026, 6:59am UTC](https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075/2 "2026-09-07T06:59:59Z")

</div>

`isEquivalentPosition` _should_ treat visually distinct positions as non-equivalent, because it is used to decide whether to normalize the selection, and the library shouldn’t leave the cursor in a place that it isn’t allowed to be in.

The code responsible for skipping things during cursor motion is in capturekeys.ts. Attached patch adjusts it to allow such elements to be cursored over in Chrome.

I’m not sure it is possible to make iOS cursor motion do the right thing here. That provides very little opportunity for scripts to influence what the cursor actually does.

> **[Make cursor motion skip node-internal uneditable nodes · 34718588e8](https://code.haverbeke.berlin/prosemirror/prosemirror-view/commit/34718588e8871f52d097f0412239afbb897945c0)**
>
> FIX: Prevent uneditable elements rendered as part of node or mark view from blocking
> cursor motion.
> 
> See https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075

---

<div class="post-metadata">

**Author:** ![ocavue](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/ocavue/32/2239_2.png) [@ocavue](https://discuss.prosemirror.net/u/ocavue)\
**Post date:** [September 7, 2026, 7:38am UTC](https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075/3 "2026-09-07T07:38:21Z")

</div>

Thanks for the quick reply!

* * *

> [@marijn](#):
>
> Attached patch adjusts it to allow such elements to be cursored over in Chrome.

Confirmed, this fixes the keyboard case in Chrome. [Video](https://github.com/issueset/repro-pm-markview-selection-ios/pull/1#issuecomment-5566804268)

* * *

> [@marijn](#):
>
> I’m not sure it is possible to make iOS cursor motion do the right thing here.

As you expected, iOS is still not working with the patch. [Video](https://github.com/issueset/repro-pm-markview-selection-ios/pull/1#issuecomment-5566835022)

In trackpad mode (holding the space bar and moving), Safari only sends `selectionchange` events to the page, no key/mouse/touch events, so you are right that there is little to hook into.

What happens on each touch move is: Safari puts the selection in a place ProseMirror does not allow, ProseMirror moves it back, and Safari puts it there again. That loop is the flicker in the video, as you can see a lot of `selectionchange` events being fired.

> [@marijn](#):
>
> the library shouldn’t leave the cursor in a place that it isn’t allowed to be in.

I am wondering whether it would be acceptable to let a node view or mark view _opt in_ to _tolerating_ some invalid DOM selection position. Simular to the existing [`relaxedSide`](https://prosemirror.net/docs/ref/#view.Decoration%5Ewidget%5Espec.relaxedSide) public API, or the private [`ViewDesc.ignoreForSelection`](https://code.haverbeke.berlin/prosemirror/prosemirror-view/src/commit/20dc0a911a79f8fc6640dbbea5e7d68d3c4784b7/src/viewdesc.ts#L589) API.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)\
**Post date:** [September 8, 2026, 11:19am UTC](https://discuss.prosemirror.net/t/caret-flickers-across-a-mark-view-with-contenteditable-false-child/9075/4 "2026-09-08T11:19:03Z")

</div>

I played around a bit more with space-bar swiping, but what I’m seeing looks pretty bleak. Android _unfocuses_ the editor element when swiping into an uneditable element. iOS doesn’t, at least, but it seems to get irrevocably stuck on such elements, even when ProseMirror isn’t resetting the selection back.
