Link widget is clicked only if editor is out of focus


I have implemented a StateField that uses Decoration.replace() to replace parts of a markdown document with actual html elements using widgets. However, if the widget is clickable (e.g. <a> links) and the editor has focus, the element needs to be clicked twice: the first time the editor just loses focus and only the second time the element is actually clicked.

Am I doing something wrong? Ideally, I’d like to click only once while preserving the editor’s focus.

Here’s a simplified version of the code I’m running

  class Link extends WidgetType {
      readonly node: SyntaxNode,
      readonly state: EditorState,
    ) {
    toDOM() {
      let link = document.createElement("a");

      let marks = this.node.getChildren("LinkMark");
      if (marks.length >= 2) {
        link.innerText = this.state.sliceDoc(marks[0].to, marks[1].from);

      let url = this.node.getChild("URL");
      if (url) link.href = this.state.sliceDoc(url.from,;

      return link;

  let decorationsField = StateField.define<DecorationSet>({
    create() {
      return Decoration.none;
    update(_, tr) {
      const builder = new RangeSetBuilder<Decoration>();
      let cursor = tr.state.selection.main.head;
        enter: (node) => {
          if ((cursor < node.from || cursor > && == "Link") {
                widget: new Link(node.node, tr.state),
            return false;
          return true;
      return builder.finish();
    provide: (f) => EditorView.decorations.from(f),

  new EditorView({
    doc: "[Test](",
    extensions: [markdown(), decorationsField],
    parent: document.body,

I was also able to reproduce this on

The issue is that your widget doesn’t define an eq method (and generally isn’t set up to be easily comparable), causing the editor to re-render it every time you recreate your decorations. This causes the link you’re clicking to disappear as the editor loses focus, breaking the browser’s native behavior.

This version works better.

I see. In the actual code, Link extends a WidgetType subclass which does define an eq method, but wasn’t working as intended.

As I’d rather not change the interface, I found that

eq(other) {
      return this.node.tree == other.node.tree;

works as expected (so far).

Thank you so much!