Different Backspace behavior at paragraph boundary depending on editor mode (merge vs delete last char)

I’m currently facing a ProseMirror behavior/design question and I’m looking for the best solution.

I have a document structure like the following (two paragraphs, each paragraph has attributes such as align):

{

“type”: “doc”,

“content”: [

{

“type”: “paragraph”,

“attr”: { “align”: “center” },

“content”: [

    { "type": "text", "text": "It was a quiet evening." }

  \]

},

{

“type”: “paragraph”,

“attr”: { “align”: “left” },

“content”: [

    { "type": "text", "text": "The sun was slowly setting beyond the hills." }

  \]

}

]

}

What I want is to support two editor “modes” when working with the same underlying structure:

  1. Paragraph-aware editor (normal behavior):

    When the cursor is at the start of the second paragraph and the user presses Backspace (delete one character backward), ProseMirror merges the paragraphs (the second paragraph node disappears and the content is joined into the first paragraph).

    This is fine for this editor.

  2. Paragraph-unaware editor (custom behavior):

    In this mode, I don’t want the paragraph node to be removed/merged at the boundary.

    Instead, when the cursor is at the start of the second paragraph and the user presses Backspace, I want it to behave as if there is no real line break there, and the editor should delete the last character of the first paragraph (without removing the paragraph node).

For the “paragraph-unaware” editor, I’m currently using NodeViews to visually make it look like there’s no line break and also make it feel like the boundary is not a “character” the user can select/delete.

I tried implementing this by overriding Backspace via a keymap plugin, but I’m not sure what the best approach is to handle all the corner cases (especially things like deleting a selection range across paragraphs, or other complex deletion scenarios).

At the same time, I still want to represent line breaks using actual paragraph nodes, because structurally I need paragraph-level attributes like align.

Question:

What would be the recommended way in ProseMirror to implement this kind of mode-dependent deletion behavior while safely covering selection/range deletion and other edge cases?

Is overriding keymaps the right direction, or is there a more robust approach (e.g. custom transaction filtering, plugins that intercept replace steps, etc.)?

Yes, you’d bind backspace to your own custom command that consults the editor mode, and runs different code depending on that mode. You can call the commands used in the default backspace binding (see docs) in the process of that, but insert your custom overridden behavior at the appropriate point between them.

Thanks for the confirmation and the detailed answer — that’s what I suspected as well.

I’ve managed to handle single-character deletion using just a keymap override, but what I’m really struggling with is how to properly control range deletion.

At the moment, for deleting a leaf node that I’ve defined as a custom node, I’m using an approach where, in appendTransaction, I check whether the deletion range includes that custom node, and if so, I restore (reinsert) the node afterward.

If I were to rely purely on keymap bindings, would that mean I’d need to fully scan everything included in the selection range starting from an empty selection, detect whether paragraphs are included, and then manually prevent or adjust the deletion logic?

Or in practice, is the appendTransaction-based approach commonly used for this kind of control?

If you want to preserve some invariant, you’ll probably want an appendTransaction hook anyway, since it’s going to be really difficult to override all possible editor actions that can delete something.

1 Like