Rich Text Editor
A rich text editor for React to create rich text content.
Introduction
This rich text editor (called as "Lyrical") is heavily built on top of Tiptap (opens in a new tab) including its Free to Pro extensions along with our own custom extensions. It is designed in a way to give as much flexibility as tiptap give us but at the same time make it easy to use and provide some common features out of the box along with styles matching our Design System. It also exposes commonly used toolbar components ready to use!
Since it is heavily built on Tiptap and majority of things are same, it is required to read the Tiptap Docs (opens in a new tab) for understanding the basics of how things work in Tiptap.
Here is a basic example of using the editor:
Dependencies
- @tiptap/core - The core of the editor
- @tiptap/react - React bindings for tiptap
- @tiptap/pm - ProseMirror core
- @tiptap/suggestion (opens in a new tab) - Suggestion utility for tiptap
- @tiptap/starter-kit (opens in a new tab) - Collection of common extensions of tiptap to get started quickly
- @tiptap/extension-blockquote (opens in a new tab) - Enables blockquote functionality
- @tiptap/extension-bullet-list (opens in a new tab) - Enables bullet list functionality
- @tiptap/extension-character-count (opens in a new tab) - Allows to count, limit and display characters in document
- @tiptap/extension-code (opens in a new tab) - Enables code mark functionality
- @tiptap/extension-code-block (opens in a new tab) - Allows to add codeblock in document
- @tiptap/extension-collaboration (opens in a new tab) - Enables collaborative editing in document
- @tiptap/extension-collaboration-cursor (opens in a new tab) - Enables realtime cursor position in document
- @tiptap-pro/extension-collaboration-history (opens in a new tab) - Enables to keep track of changes in document
- @tiptap/extension-color (opens in a new tab) - Enables color mark functionality
- @tiptap/extension-dropcursor (opens in a new tab) - Enables drop cursor functionality
- @tiptap/extension-font-family (opens in a new tab) - Add supports for multiple font family in document
- @tiptap/extension-gapcursor (opens in a new tab) - Enables gap cursor functionality
- @tiptap/extension-hard-break (opens in a new tab) - Enables hard break functionality
- @tiptap/extension-highlight (opens in a new tab) - Enables highlight mark functionality
- @tiptap/extension-horizontal-rule (opens in a new tab) - Enables horizontal rule functionality
- @tiptap/extension-image (opens in a new tab) - Enables to render image in document
- @tiptap/extension-link (opens in a new tab) - Allow to link up text
- @tiptap/extension-list-item (opens in a new tab) - Add supports for adding list elements
- @tiptap/extension-mention (opens in a new tab) - Enables to mention users in document
- @tiptap/extension-ordered-list (opens in a new tab) - Enables ordered list functionality
- @tiptap/extension-placeholder (opens in a new tab) - Enables to add placeholder in document
- @tiptap/extension-subscript (opens in a new tab) - Enables subscript mark functionality
- @tiptap/extension-superscript (opens in a new tab) - Enables superscript mark functionality
- @tiptap/extension-table (opens in a new tab) - Add supports for adding table in document
- @tiptap/extension-table-cell (opens in a new tab) - Helper of table extension, used to add table cell
- @tiptap/extension-table-header (opens in a new tab) - Helper of table extension, used to add table header
- @tiptap/extension-table-row (opens in a new tab) - Helper of table extension, used to add table row
- @tiptap/extension-task-item (opens in a new tab) - Enables to add task item in document, helper of task list extension
- @tiptap/extension-task-list (opens in a new tab) - Enables to add task list in document
- @tiptap/extension-text-align (opens in a new tab) - Enables to add text alignment in document
- @tiptap/extension-text-style (opens in a new tab) - Allows to add text style in document
- @tiptap/extension-typography (opens in a new tab) - Transforms common characters into typographic characters
- @tiptap/extension-underline (opens in a new tab) - Enables underline mark functionality
- @tiptap/extension-youtube (opens in a new tab) - Allows to embed youtube video in document
- @tiptap-pro/extension-ai (opens in a new tab) - Enables to add AI generated content and images in document
- @tiptap-pro/extension-details (opens in a new tab) - Enables to add details element in document
- @tiptap-pro/extension-details-content (opens in a new tab) - Helper extension for details extension to insert content
- @tiptap-pro/extension-details-summary (opens in a new tab) - Helper extension for details extension to insert summary
- @tiptap-pro/extension-emoji (opens in a new tab) - Renders emoji as inline node in Document
- @tiptap-pro/extension-invisible-characters (opens in a new tab) - Shows non-printable characters in document
- @tiptap-pro/extension-mathematics (opens in a new tab) - Enables to add math equations in document
- @tiptap-pro/extension-file-handler (opens in a new tab) - Enables to add file upload functionality in document
- katex (opens in a new tab) - Used by mathematics extension to render math equations
- tippy.js (opens in a new tab) - Used by several extensions to show suggestions
Usage
Using Editor
In Tiptap we used to create new editor instance by using the useEditor
hook from @tiptap/react
package. But in our case
we expose our own hook called useLyrical
which is a wrapper around useEditor
hook and provides some extensions out of the box.
The best part here is that you got the flexibility of doing anything you used to do with useEditor
hook!
See the API Referece of editor
instance here.
Here is a comparison of useEditor
and useLyrical
hooks and it's basic implementation.
import { EditorContent, useEditor } from "@tiptap/react"
export const WithTiptap = () => {
const editor = useEditor()
return <EditorContent editor={editor} />
}
import {
LyricalContent,
LyricalProvider,
useLyrical,
} from "@inspectra/ui/lyrical"
export const Withyrico = () => {
const editor = useLyrical()
return (
<LyricalProvider editor={editor}>
<LyricalContent />
</LyricalProvider>
)
}
Using the editor instance from useLyrical
hook provides some default extensions out of the box. Here is the list of extensions
that are included in it:
- Blockquote (opens in a new tab)
- BulletList (opens in a new tab)
- Text Align (opens in a new tab)
- Ordered List (opens in a new tab)
- Text style (opens in a new tab)
- Highlight (opens in a new tab)
- Code (opens in a new tab)
- Code Block (opens in a new tab)
- Horizontal Rule (opens in a new tab)
- Hard Break (opens in a new tab)
- Subscript (opens in a new tab)
- Superscript (opens in a new tab)
- Link (opens in a new tab)
- Typography (opens in a new tab)
- Dropcursor (opens in a new tab)
- Gapcursor (opens in a new tab)
- Underline (opens in a new tab)
Using createExtension
This is a utility function that we expose to initiate an extension. It accepts the name of the extension as a string and returns the extension for further configuration. It also provides auto suggestion of all available extensions.
By default, the function needs the extension name as the first parameter but it also supports an optional second parameter that allows to set the initial configuration of the extension, as we would do with createExtension("extensionName").configure({...})
.
Therefore, we can use it in two ways:
- By setting the configuration values inside the second parameter of the function.
- By setting the configuration values using the
configure
method of the extension.
Remember that if we use both cases at the same time, the configuration values of the second parameter of the function will be stepped on by the configuration values of the configure
method of the extension.
import {
LyricalContent,
LyricalProvider,
useLyrical,
} from "@inspectra/ui/lyrical"
import { createExtension } from "@inspectra/ui/lyrical/extensions"
export const Withyrico = () => {
const editor = useLyrical({
extensions: [
createExtension("color"),
createExtension("youtube"),
createExtension("table")
.configure({
resizable: true, // <------------- configuration
expandable: true,
})
.extend({
// <------------- extend
}),
createExtension("table-row"),
createExtension("table-cell"),
createExtension("table-header"),
],
})
return (
<LyricalProvider editor={editor}>
<LyricalContent />
</LyricalProvider>
)
}
Example
Besides these core utilities and extensions, we also expose a LyricalProvider
which will be used as a React Provider context to share editor
instance
among its childrens which are coming from lyrical. This opnes the door of using our pre-built toolbar components!
Here is a basic implementation of using the editor with some toolbar components:
It is good to know we are not limited to use only these toolbar components.
Since we have access to the editor
instance we can create any custom toolbar
as our need!
import {
LyricalContent,
LyricalProvider,
useLyrical,
} from "@inspectra/ui/lyrical"
import {
BoldToolbar,
HeadingsToolbar,
ItalicToolbar,
UnderlineToolbar,
} from "@inspectra/ui/lyrical/components"
import { createExtension } from "@inspectra/ui/lyrical/extensions"
export const Withyrico = () => {
const editor = useLyrical({
extensions: [
createExtension("color"),
createExtension("youtube"),
createExtension("table").configure({
resizable: true,
}),
createExtension("table-row"),
createExtension("table-cell"),
createExtension("table-header"),
],
})
return (
<LyricalProvider editor={editor}>
// <------------- the toolbar components ------------>
<HeadingsToolbar />
<BoldToolbar />
<ItalicToolbar />
<UnderlineToolbar />
<LyricalContent />
</LyricalProvider>
)
}
Extensions
Extensions are the building blocks of the editor. They are the ones that provide the functionality to the editor.
Here we demonstrate the full list of extensions that are available in lyrical.
How to use an extension?
Using an extension is very easy. We exposed a utility funtion called createExtension
which will give auto suggestions of all the available extensions.
It accepts the name of the extension as a string and returns the extension for further configuration.
By registering an extension name inside createExtension
function, it initiate the extension and make the functionality
ready to use! Yet, it is also possible to create completely custom extension and use it in the editor. For the guide
of creating new extension, read here
Here is a basic example of using avialable extensions:
import {
LyricalContent,
LyricalProvider,
useLyrical,
} from "@inspectra/ui/lyrical"
import { createExtension } from "@inspectra/ui/lyrical/extensions"
export const Withyrico = () => {
const editor = useLyrical({
extensions: [
// <------------- using extensions ------------>
createExtension("color"),
createExtension("youtube"),
createExtension("table").configure({
resizable: true,
}),
createExtension("table-row"),
createExtension("table-cell"),
createExtension("table-header"),
],
})
return (
<LyricalProvider editor={editor}>
<LyricalContent />
</LyricalProvider>
)
}
Creating Custom Extension
Creating a cusotm extension can be difficult. So it is important to understand the basics of how extension work in tiptap. Here is the full documentation of creating custom extension on Tiptap Docs (opens in a new tab).
Apart from it, the extension creating process is same but in our case we don't need to install the tiptap core packages.
We already exported all the core packages like @tiptap/core
as @inspectra/ui/lyrical/core
, @tiptap/pm
as
@inspectra/ui/lyrical/pm
and @tiptap/react
as @inspectra/ui/lyrical/react
and @tiptap/suggestion
as @inspectra/ui/lyrical/suggestion
. So we can use directly like this:
import { LyricalCore } from "@inspectra/ui/lyrical/core"
export const CustomExtension = LyricalCore.Node.create({
name: "custom-image",
// other code goes here
})
This is the basic structure of creating a custom extension. And we can use it just like this:
const editor = useLyrical({
extensions: [
CustomExtension, // <------------- using custom extension
createExtension("color"),
createExtension("youtube"),
],
})
Suggestions
We refer to suggestions as the list of items that appear when you type a special character such as @
or /
in the editor. These suggestions can be used to mention users (or anything else) or to display shortcuts to the editor elements.
To use suggestions, we have two types of extensions that allow you to add suggestions
:
mention
slash-command
.
Both extensions can be used normally with createExtension
. However, a new utility is added to facilitate their use: createSuggestion
. This utility allows us to create suggestions in a simpler way without having to think about the development of the list of items and its configuration to work with the editor. For this we must pass as first parameter the name of the base extension.
- If we create suggestions for mentions, by default it will offer us 5 generic names as initial sample.
- If we create suggestions for commands, it will default to 10 quick access commands:
p
,all-list
,h1
,h2
,h3
,bullet-list
,numbered-list
,quote
,code
, andtable
.
To change them, we can pass an array of values inside the second function parameter.
The second parameter of the function is optional and allows us, in addition to the default values offered by the suggestion
key, to add custom values.
- If the array is for an extension of type
mention
, then the values must be an array of strings. - If the array is for a
slash-command
type extension, then the values must be an array of objects with the following structure:
Array<{
title: string
description: string
searchTerms: string[]
icon: JSX.Element
command?: MentionOptions["suggestion"]["command"]
}>
The use of createSuggestion
is optional. The purpose of this utility is to
be able to summarize the creation of suggestions in a few lines of code.
Example of use:
...
createExtension("mention", {
suggestion: createSuggestion("mention")
}),
...
If we would like to add custom values:
...
createExtension("mention", {
suggestion: createSuggestion("mention", {
values: ["value1", "value2", "value3"]
})
}),
...
In addition, if we would like to customize the configuration, we can use the same properties that are used inside the default suggestion
object inside the options
object:
...
createExtension("mention", {
suggestion: createSuggestion("mention", {
options: {
items: ...,
...
}
})
}),
...
This is equivalent to doing it in the following way, but with the disadvantage that we will have to develop the whole operation from scratch:
...
createExtension("mention", {
suggestion: {
items: ...,
...
}
}),
...
API Reference
LyricalProvider
Prop | Type | Default |
---|---|---|
editor* | Editor | ─ |
LyricalContent
This component is used to render the content of the editor. It is a wrapper around EditorContent
component of tiptap.
It accepts all the props that a HTMLDivElement
accepts.
LyricalProvider
component.useLyrical
This is a wrapper around useEditor
hook of tiptap. It accepts all the props that useEditor
accepts.
It extends the extensions
array with some of our own extensions and also provides some default options to the editor.
View all the available extensions here on Tiptap Docs (opens in a new tab).
LyricalSelectionMenu
This component is used to render the selection menu of the editor. It is a wrapper around BubbleMenu
component of tiptap.
See the full list of props here on Tiptap Docs (opens in a new tab).
LyricalProvider
component.Editor
This is the editor instance that is returned by useLyrical
hook. It is a wrapper around Editor
instance of tiptap.
Here is the full documentation of Editor
instance on Tiptap Docs (opens in a new tab).