How to quickly add a rich text editor to your Next.js project using TipTap
Published: 2024-06-05 03:20:04

[Tiptap](https://tiptap.dev/) is an open source headless wrapper around [ProseMirror](https://prosemirror.net/). 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](https://tiptap.dev/docs/editor/api/extensions/history) 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 text 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](https://yjs.dev/). You can head to [Tiptap's official docs](https://tiptap.dev/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 Next.js project.

You can hear more from me on: 
[Twitter (X)](https://x.com/code_withjoseph) | [Instagram](https://www.instagram.com/codewithjosephwebdev)