A Guide on using Tiptap in a React CRUD app.
What Is Tiptap
Tiptap, one of the leading open-source dev tools for rich content editors is trusted by many companies like Substack, AxiosHQ, and GitLab.1
They have accumulated over 2 million downloads1 with their cool products such as Tiptap Content AI and Tiptap Collaboration.
However, for this guide, we will focus on the Tiptap Editor.
Tiptap Editor is a headless toolkit for building rich text WYSIWYG editors for Vue.js and React.js applications. Giving the user the complete freedom to fashion their own editor interface using customizable building blocks. It offers sensible defaults, an extensive array of extensions, and an intuitive API for tailoring every facet to the user’s preferences.
Why Use Tiptap
If you are looking for a powerful text editor for web applications, Tiptap is a great option to consider. Being open-source and built on the well-regarded ProseMirror library,2 Tiptap offers a solid foundation. It features real-time collaboration and boasts a rich library of community extensions that includes an extension that enables Markdown support. Starting with Tiptap is easy thanks to its free tier, but for those needing advanced features like AI integration and document commenting, paid plans are available, making Tiptap a versatile solution for diverse project needs.
Relevant Links
For comprehensive resources related to Tiptap Editor, consider the following links:
-
Tiptap Official Website: Detailed information on features and how to get started with Tiptap. Visit Tiptap
-
Documentation and Examples: Access to extensive documentation and practical examples for using Tiptap. Explore the docs and View examples
-
GitHub Repository: The source code and contribution guidelines are available on GitHub. Check out the repository
-
Guide and Tutorials on GitHub: A detailed guide offering insights into configuring the editor, working with extensions, and more. Read the guide
-
Community Discussions: Engage with the Tiptap community on GitHub for help and discussions. Join the conversation
Requirements
There are multiple methods to install Tiptap, each tailored to a specific use case. Detailed integration guides for different project types can be accessed here. In this article, we’ll focus on creating a React project integrated with Tiptap, so you’ll need Node.js and npm; if you don’t have them installed, follow this guide to install them.
Intro to Tiptap in React
1. Create a project
You may use any other method of creating a React app if you wish, but Tiptap Create React App template by @alb is the quickest way.
npx create-react-app my-tiptap-project --template tiptap
This will also install the dependencies for you!
2. Run the project
Start your React application to see Tiptap in action.
cd my-tiptap-project
npm start
3. Add a menu bar
Improve your editor by adding a menu bar to Tiptap.jsx.
Each button in the menu bar example code below takes three arguments. Taking the bold button as an example:
- The
onClick
function makes the editor toggle any selected text between bolded and non-bolded modes. - The
disabled
argument determines whether the bold button should be faded out because the selected text cannot be bolded. - The
className
isis-active
or blank depending on whether the selected text is bolded. If theis-active
className
is present, the bold button in the menu bar will be emphasized.
Here is the code sourced from here:
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
import { EditorProvider, useCurrentEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React from 'react'
const MenuBar = () => {
const { editor } = useCurrentEditor()
if (!editor) {
return null
}
return (
<>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={
!editor.can()
.chain()
.focus()
.toggleBold()
.run()
}
className={editor.isActive('bold') ? 'is-active' : ''}
>
bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
disabled={
!editor.can()
.chain()
.focus()
.toggleItalic()
.run()
}
className={editor.isActive('italic') ? 'is-active' : ''}
>
italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
disabled={
!editor.can()
.chain()
.focus()
.toggleStrike()
.run()
}
className={editor.isActive('strike') ? 'is-active' : ''}
>
strike
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
>
h1
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
>
h2
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
>
h3
</button>
<button
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''}
>
code block
</button>
<button
onClick={() => editor.chain().focus().undo().run()}
disabled={
!editor.can()
.chain()
.focus()
.undo()
.run()
}
>
undo
</button>
<button
onClick={() => editor.chain().focus().redo().run()}
disabled={
!editor.can()
.chain()
.focus()
.redo()
.run()
}
>
redo
</button>
<button
onClick={() => editor.chain().focus().setColor('#958DF1').run()}
className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
>
purple
</button>
</>
)
}
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false,
},
orderedList: {
keepMarks: true,
keepAttributes: false,
},
}),
]
const content = `
<h2>
Hi there,
</h2>
<p>
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
</p>
<pre><code class="language-css">body {
display: none;
}</code></pre>
<p>
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
</p>
`
export default function Tiptap () {
return (
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
)
}
4. Install new dependencies
Install tiptap/extension-color and tiptap/extension-text-style packages.
npm install @tiptap/extension-color @tiptap/extension-text-style
5. Enjoy Your new editor
npm start
6. Use the output from the editor
At the bottom of Tiptap.jsx, add an onUpdate
prop to the EditorProvider, using the getHTML()
and getJSON()
method on the editor object to retrieve the HTML and JSON representations, respectively, of the content in the editor when the user changes it and print it to the browser’s console.
export default function Tiptap () {
return (
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content} onUpdate={({ editor }) => {
const json = editor.getJSON();
const html = editor.getHTML();
console.log(json);
console.log(html);
}}></EditorProvider>
)
}
Example with Markdown Editor in React
By default, Tiptap doesn’t support input from or output to Markdown files.3 However, there is a community-developed npm package called tiptap-markdown
that extends Tiptap to enable this. We’ll go through the installation of this package and the creation of a simple editor that uses it.
1. Install tiptap-markdown
We’ll continue with the React project we created above. Install the tiptap-markdown
package using npm:
npm install tiptap-markdown
2. Import the package
Make a copy of Tiptap.jsx called Markdown.jsx. Add this import to the top of the file:
import { Markdown } from 'tiptap-markdown';
3. Edit the editor
In Markdown.jsx, add Markdown
to the list of extensions used by the Tiptap editor. Since the input file format has changed from HTML to Markdown, we’ll change the initial content as well:
const extensions = [
[...keep the existing extensions...]
Markdown,
]
const content = `
## Hi again,
Here is a Markdown version!
\`\`\`css
body {
display: none;
}
\`\`\`
`
4. Render the new editor
At the top of App.js, import the new Markdown editor from Markdown.jsx:
import Markdown from './Markdown';
Finally, render the new Markdown editor below the existing editor in App.js:
const App = () => {
return (
<div className="App">
<Tiptap />
<Markdown />
</div>
);
};
5. See both editors in action
npm start
6. Use the output from the Markdown editor
At the bottom of Markdown.jsx, add an onUpdate
prop to the EditorProvider, using the getMarkdown()
method on the editor object to retrieve the Markdown representation of the content in the editor when the user changes it and print it to the browser’s console.
export default function Tiptap () {
return (
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content} onUpdate={({ editor }) => {
const new_markdown = editor.storage.markdown.getMarkdown();
console.log(new_markdown);
}}></EditorProvider>
)
}
Citations
https://tiptap.dev/blog/insights/tiptap-seriesseed
https://tiptap.dev/product/editor
https://tiptap.dev/docs/editor/introduction
https://tiptap.dev/docs/editor/installation/react
https://github.com/alb/cra-template-tiptap
https://tiptap.dev/product/editor
https://tiptap.dev/docs/editor/guide/output