Rich Text Editor
Used to create and format text content visually, built on Tiptap.
Getting Started
Add the snippet
The rich text editor is exposed as a snippet that can be added to your project.
npx @chakra-ui/cli snippet add rich-text-editorTiptap StarterKit
To get started with the core editor features, install the Tiptap StarterKit.
npm i @tiptap/starter-kitAdditional extensions
Tiptap provides a rich set of additional extensions for adding additional features to the editor. The most commonly used additional extensions you can install are:
- Subscript:
@tiptap/extension-subscript - Superscript:
@tiptap/extension-superscript - Text Align:
@tiptap/extension-text-align - Text Style:
@tiptap/extension-text-style
npm i @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-text-align @tiptap/extension-text-styleUsage
import { Control, RichTextEditor } from "@/components/ui/rich-text-editor"
import { useEditor } from "@tiptap/react"<RichTextEditor.Root editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.ControlGroup>
<Control.Bold />
<Control.Italic />
<Control.Underline />
</RichTextEditor.ControlGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor.Root>Examples
Toggle Edit Mode
In the useEditor hook, assign the editable property to control the editor's
mode. When set to false, the editor will be in view-only mode.
Controlled
In the useEditor hook, set the content and onUpdate properties to control
the editor's content programmatically.
const [content, setContent] = useState("<p>Edit here...</p>")
const editor = useEditor({
content,
onUpdate({ editor }) {
setContent(editor.getHTML())
},
})Placeholder
To add a placeholder to the editor, use the
@tiptap/extension-placeholder
extension and configure the placeholder property.
const editor = useEditor({
extensions: [
// ... other extensions
Placeholder.configure({
placeholder: "Start typing your content here...",
}),
],
})Character Count
To display live character and word counts, use the @tiptap/extensions/character-count extension. This is especially useful for editors with limits or word-count requirements.
const editor = useEditor({
extensions: [
// ... other extensions
CharacterCount.configure({
limit: 1000,
mode: "textSize",
}),
],
})Live Preview
Use the editor's getHTML() method to retrieve content and display it in a
read-only panel.
Text Highlight
To add text highlighting, use the
@tiptap/extension-highlight
extension and configure the multicolor property. This allows users to pick or
cycle through highlight colors via the <Control.Highlight /> component.
Bubble Menu
Use the BubbleMenu component from Tiptap with any existing controls. The menu
will appear above any text selection, providing contextual formatting options.
Autosave
Implement an autosave feature by using the editor's onUpdate method. This
allows you to handle content changes and save them to a server, local storage,
or any other persistence layer.
Task List
To add interactive task lists, use the
@tiptap/extension-task-item
and
@tiptap/extension-task-list
extensions and configure the nested property.
Code Blocks
Add syntax-highlighted code blocks using
@tiptap/extension-code-block-lowlight
and lowlight to highlight your favorite languages.
Drag Handle
To add drag-and-drop reordering, use the @tiptap/extension-drag-handle-react. This extension enables draggable handles for each block, letting users easily reorder content.
Images
To add images, use the @tiptap/extension-image extension. This lets you embed image URLs, upload files, or integrate a custom media service.
Hashtags
To support hashtags in the editor, create a custom Tiptap node. This allows hashtags to be parsed, rendered, and handled as structured inline content.
Mentions
Here's an example of how to add mentions to the editor by creating a custom
Tiptap extension that triggers on @ and renders a suggestion menu using the
provided menu components.
Emojis
Enhance your editor with emoji suggestions by using Tiptap's
Emoji extension. Emojis
can be triggered by typing : or using common emoticons like :) or <3.
Slash Commands
Enable slash commands in your editor by creating a Tiptap extension that
triggers on /.
Composition
A real-world Google Docs–like layout demonstrating a full-page editor with a collapsible document outline, sticky toolbar, floating link menus, and integrated controls for headings, lists, links, images, and text formatting.
Guides
Adding controls
RichTextEditor ships with a set of built-in controls that can be composed
inside RichTextEditor.ControlGroup.
import { Control } from "@/components/ui/rich-text-editor"<RichTextEditor.ControlGroup>
<Control.Bold />
<Control.Italic />
<Control.Strike />
</RichTextEditor.ControlGroup>Customizing Content Padding
The editor uses CSS custom properties for content padding:
<RichTextEditor.Root
editor={editor}
css={{
"--content-padding-x": "spacing.8",
"--content-padding-y": "spacing.6",
"--content-min-height": "sizes.96",
}}
>
<RichTextEditor.Content />
</RichTextEditor.Root>Custom Controls
The RichTextEditor provides three factory functions for creating custom
controls that integrate seamlessly with the editor: createBooleanControl,
createSelectControl, and createSwatchControl.
Boolean Controls
Boolean controls toggle editor states (bold, italic, etc.) and are the most common control type:
import { createBooleanControl } from "@/components/ui/rich-text-editor"
import { LuSparkles } from "react-icons/lu"
export const CustomHighlight = createBooleanControl({
label: "Highlight Important",
icon: LuSparkles,
command: (editor) => {
editor
.chain()
.focus()
.toggleMark("textStyle", {
backgroundColor: "#fef08a",
fontWeight: "bold"
})
.run()
},
getVariant: (editor) => {
const attrs = editor.getAttributes("textStyle")
return attrs.backgroundColor === "#fef08a" ? "subtle" : "ghost"
},
isDisabled: (editor) => !editor.can().toggleMark("textStyle")
})
// Use it in your toolbar
<RichTextEditor.ControlGroup>
<CustomHighlight />
</RichTextEditor.ControlGroup>Select Controls
Select controls provide dropdown menus for choosing between multiple options:
import { createSelectControl } from "@/components/ui/rich-text-editor"
export const LineHeight = createSelectControl({
label: "Line Height",
width: "100px",
placeholder: "Normal",
options: [
{ value: "normal", label: "Normal" },
{ value: "1.5", label: "1.5" },
{ value: "2", label: "Double" },
{ value: "2.5", label: "2.5" },
],
getValue: (editor) => {
return editor.getAttributes("textStyle")?.lineHeight || "normal"
},
command: (editor, value) => {
if (value === "normal") {
editor.chain().focus().unsetMark("textStyle").run()
} else {
editor.chain().focus().setMark("textStyle", { lineHeight: value }).run()
}
},
renderValue: (value, option) => {
return <Box fontWeight="medium">{option?.label || "Normal"}</Box>
},
})Swatch Controls
Swatch controls provide color picker interfaces with predefined color swatches:
import { createSwatchControl } from "@/components/ui/rich-text-editor"
import { LuPaintbrush } from "react-icons/lu"
export const BackgroundColor = createSwatchControl({
label: "Background Color",
icon: LuPaintbrush,
swatches: [
{ value: "#fef3c7", color: "#fef3c7", label: "Yellow" },
{ value: "#dbeafe", color: "#dbeafe", label: "Blue" },
{ value: "#dcfce7", color: "#dcfce7", label: "Green" },
{ value: "#fce7f3", color: "#fce7f3", label: "Pink" },
],
getValue: (editor) => {
return editor.getAttributes("textStyle")?.backgroundColor || ""
},
command: (editor, color) => {
editor
.chain()
.focus()
.setMark("textStyle", { backgroundColor: color })
.run()
},
getProps: (editor) => ({
variant: editor.getAttributes("textStyle")?.backgroundColor
? "subtle"
: "ghost",
}),
showRemove: true,
onRemove: (editor) => {
editor
.chain()
.focus()
.updateAttributes("textStyle", { backgroundColor: null })
.run()
},
})