How to quickly add a rich text editor to your Next.js project using TipTap

RMAG news

Tiptap is an open source headless wrapper around ProseMirror. ProseMirror is a toolkit for building rich text WYSIWYG editors. The best part about Tiptap is that it’s headless, which means you can customize and create your rich text editor however you want. I’ll be using TailwindCSS for this tutorial.

How to get Tiptap working

Step 1 – Install Tiptap into your project

Tiptap is framework-agnostic, it integrates with many popular frameworks, even PHP and vanilla JavaScript.
There are three packages (@tiptap/react, @tiptap/pm, and @tiptap/starter-kit) you need to install that provide all the necessary extensions for you to add Tiptap to your Next.js project.

npm install @tiptap/react @tiptap/pm @tiptap/starter-kit

Step 2 – Create a Tiptap component

To use Tiptap in your next.js project, you need to add a new Component. And add this piece of code to it.

‘use client’

import { useEditor, EditorContent } from ‘@tiptap/react’
import StarterKit from ‘@tiptap/starter-kit’

const Tiptap = () => {
const editor = useEditor({
extensions: [
StarterKit,
],
content: ‘<p>Hello World! 🌎️</p>’,
})

return (
<EditorContent editor={editor} />
)
}

export default Tiptap

Let me break it down. Starterkit is a collection of the most useful extensions in Tiptap for getting started. Extensions are a powerful way to extend the functionality of your Tiptap editor, for example, the History plugin that helps you track changes when editing your document and provides redo and undo options. useEditor is a hook provided by Tiptap that it initializes an editor instance. This instance provides the interface through which you can interact with the editor’s functionality. EditorContent binds your editor instance and the DOM element where your content is rendered. The content property is optional, but it determines the initial state of your editor. That’s all you need to get your rich editor up and running with Tiptap.

Customizing your editor

Since Tiptap comes raw, it allows you to choose how your editor looks like. By default, it doesn’t come with a menu bar for editing your document, but you can add it by using the slotBefore prop in EditorContent. What the slotBefore prop does is that it tells EditorContent that there should be some content at the top of the editor. To create our menu bar, we can just use buttons. Let’s create a menu bar and add a button to toggle boldness.

<div className=”flex flex-wrap gap-2″>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={!editor.can().chain().focus().toggleBold().run()}
className={editor.isActive(“bold”) ? “is-active” : “”}
>
bold
</button>
</div>

That feels like a lot, doesn’t it? Let me take it slowly. The commands that are passed into the onClick prop, and the disabled prop are a chain of commands. Each of them is explained below:

editor should be a Tiptap instance,

chain() is used to tell the editor you want to execute multiple commands,

focus() sets the focus back to the editor,

toggleBold() marks the selected text bold, or removes the bold mark from the text selection if it’s already applied and

run() will execute the chain.

isActive() checks if something is applied to the selected text already.

!editor.can(): Checks if the toggleBold command can be executed. If it can’t, the ! operator negates the result.

I’m also using an is-active class to provide UI feedback when the button is toggled.

Now, the Tiptap component should look like this:

‘use client’

import { useEditor, EditorContent } from ‘@tiptap/react’
import StarterKit from ‘@tiptap/starter-kit’

const Tiptap = () => {
const editor = useEditor({
extensions: [
StarterKit,
],
content: ‘<p>Hello World! 🌎️</p>’,
})

return (
<EditorContent editor={editor} slotBefore={<div className=”flex flex-wrap gap-2″>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={!editor.can().chain().focus().toggleBold().run()}
className={editor.isActive(“bold”) ? “is-active” : “”}
>
bold
</button>
</div>} />
)
}

export default Tiptap

You can add more buttons to toggle more things in your text like headings, paragraphs, redo/undo, blockquotes, bullet lists, and a lot more.

(Optional) Use the @tailwindcss/typography to get the best typography styles for your editor

The @tailwindcss/typography plugin applies typography styles to your HTML elements that follow best practices. By default, TailwindCSS applies minimalistic styles and it may not include detailed typography styles. The content on your Tiptap editor is rendered as HTML by using this plugin you’re telling your editor to inherit the typography styles provided by TailwindCSS such as margins, font styles, etc.

npm install –save-dev @tailwindcss/typography

You can use it by installing and then adding it to your list of plugins in your tailwind.config.ts or tailwind.config.js file:

import { defineConfig } from ‘tailwindcss’;

export default defineConfig({
mode: ‘jit’,
purge: [‘./src/**/*.{js,jsx,ts,tsx}’, ‘./public/index.html’],
darkMode: false, // or ‘media’ or ‘class’
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [
require(‘@tailwindcss/typography’), // Add the typography plugin
// other plugins
],
});

All you have to do is to add the plugin. Your Tailwind config file might look different from this, but it’s only the plugin that’s necessary.

Generating an output from your editor

There are two ways you can get your content from Tiptap. The first way is through JSON, and the second option is with HTML. Tiptap doesn’t support Markdown as an output format. But it supports Markdown when you’re editing your content. Here’s an example in code:

const json = editor.getJSON()

And for HTML:

const html = editor.getHTML()

You can also listen for changes to store your content continuously as it changes. By adding the onUpdate hook provided by Tiptap that is triggered on every change, generating the data, and storing it:

const editor = useEditor({
extensions: [
StarterKit,
],
// triggered on every change
onUpdate: ({ editor }) => {
const json = editor.getJSON()
// send the content to an API here
},
content: ‘<p>Hello World! 🌎️</p>’,
})

There’s also one last option for storing your content, which is y.js. You can head to Tiptap’s official docs to learn more about storing your content and how to customize it better.

That’s it, that’s all you need to get started with Tiptap in your Nex.js project.

You can hear more from me on:
Twitter (X) | Instagram