BlogJavaScript

Adding a page break to Tiptap

Written by Codemzy on November 8th, 2024

I'm adding a simple way to create a page break in Tiptap. It's not a full paged display, but it allows users to force a page break before a heading or at the end of a chapter.

I recently needed to add page breaks to Tiptap.

Tiptap is an online WYSIWYG editor, and webpages don't have page breaks. However, I was using the editor to create and download documents (kind of like how you can edit Google Docs online), and I wanted users to be able to insert page breaks where they wanted them.

Luckily, it's pretty easy to add custom features to Tiptap.

To be clear up front, I didn't create a "paged" UI like Google Docs - just a way to add and remove user-defined page breaks. If the content is long enough, other page breaks would be automated.

See the Pen Untitled by Codemzy (@codemzy) on CodePen.

Add the horizontal rule extension

I decided to extend the horizontal rule extension to create my page breaks. So (assuming you already have tiptap installed), go ahead and add the horizontal rule extension.

import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import HorizontalRule from '@tiptap/extension-horizontal-rule';

new Editor({
  element: document.querySelector('.editor'),
  extensions: [
    StarterKit,
    HorizontalRule
  ],
  content: '<p>Hello World!</p>',
});

Extend the horizontal rule extension

Next, I extended the horizontal rule element to take a data-type attribute. This is because I needed both horizontal rules and page breaks in my editor.

Another option is to create a totally custom <pagebreak></pagebreak> tag.

Or, if you only need page breaks, you could skip this section and just repurpose the hr tag as a page break using the CSS in the next section.

import HorizontalRule from '@tiptap/extension-horizontal-rule';

const CustomHorizontalRule = HorizontalRule.extend({
  addAttributes() {
    return {
      type: {
        default: null,
        // Customize the HTML parsing (for example, to load the initial content)
        parseHTML: element => element.getAttribute('data-type'),
        // … and customize the HTML rendering.
        renderHTML: attributes => {
          if (attributes.type) {
            return {
              'data-type': attributes.type
            }
          }
        },
      },
    }
  },
});

export default CustomHorizontalRule;

Add a page break button

Let's add a page break button to the editor that inserts a custom hr element with a data-type="pagebreak" attribute.

<button id="editor-button-pagebreak">Page Break</button>

For the sake of this blog post, I'll use vanilla JavaScript to add the "click" event listener.

document.getElementById('editor-button-pagebreak').addEventListener('click', function() {
  editor.chain().focus().insertContent('<hr data-type="pagebreak" /><p></p>').run();
});

Add some custom CSS

We've not actually done anything to create our page break yet. We've just added another hr element that doesn't look or act any different to the standard one!

But it does have that data-type="pagebreak" attribute.

Let's target that attribute with some CSS to make sure that when the content in the editor is printed, the page break is enforced.

First, let's show the page break as a dashed line in the editor:

hr {
  display: block; 
  padding-bottom: 0.25rem; 
  margin-top: 5px;
  margin-bottom: 5px; 
  border-top-width: 1px; 
  border-color: #E5E7EB; 
}

hr[data-type="pagebreak"] {
  border-top-width: 2px; 
  border-style: dashed;
  margin-top: 10px;
  margin-bottom: 10px; 
}

Now we need to hide the page break when it's printed, but make the content break to a new page at that point. We can use @media print to remove the border, margins and padding, and add the break.

@media print {
  hr[data-type="pagebreak"] {
    break-before: always;
    page-break-before: always;
    padding-bottom: 0; 
    margin-top: 0;
    margin-bottom: 0; 
    border-width: 0; 
  }
}

You can also remove page breaks by selecting them and hitting the back button. It would be nice to see when our page breaks are selected. Let's make the page break blue when it's selected in the editor.

hr.ProseMirror-selectednode {
  border-color: #93C5FD;
}

It's not quite Google Docs, but it does allow page breaks to be inserted!