How best to preserve `.ProseMirror-trailingBreak` in output?

This is similar to other questions about .ProseMirror-trailingBreak (An extra <br> is added in certain pasted content), but I think is unique enough to warrant its own topic.

Our schema uses divs as the paragraph element, because we need to rely on browser defaults for styling (we don’t know where the output will be rendered) and we do not want paragraphs to have top or bottom margins. The goal is to match how gmail’s formatting works (visually, a new paragraph and a hard break look the same)

Where I’ve landed is that we actually want the extra break tag that Prosemirror inserts, to be a part of the schema and be included in the output. Otherwise, hitting enter to create a new <div></div> “paragraph” takes up no vertical space.

But, if we force some vertical space with <div><br></div> on Enter, we end up with two breaks (our own, and .ProseMirror-trailingBreak) which, like others have noted, doesn’t match the output, which confuses people.

My solution so far is an appendTransaction plugin that marks empty paragraphs with an attribute. This attribute results in a data-attribute which on output we use to manually insert a <br> into each of the empty paragraphs (inspired by this comment):

export const MarkEmptyParagraphsPlugin = new Plugin({
  appendTransaction(transactions, _, nextState) {
    const docChanged = transactions.some(
      (transaction) => transaction.docChanged,

    // bail if it's not a change to the actual content
    if (!docChanged) {
      return null;

    const { tr } = nextState;

    let modified = false;

    nextState.doc.descendants((node, pos) => {
      const { paragraph } = nextState.schema.nodes;

      if (node.type !== paragraph) {

      const { attrs, content } = node;

      tr.setNodeMarkup(pos, undefined, {
        // we use this to modify the output HTML string and insert a break tag
        isEmpty: content.size === 0,

      modified = true;

    return modified ? tr : null;

This works OK, but exposes other complexity (HTML that includes <div><br></div> needs to be parsed specially so we can avoid the double-break issue)

Is there a more Prosemirror-native approach here?

The easiest thing to do is probably to run an extra function over the output of DOMSerializer, checking for empty paragraph divs or paragraph divs that end in a <br>, and appending a <br> to those.

1 Like