apps/www/content/docs/components/rich-text-editor.mdx
::::steps
The rich text editor is exposed as a snippet that can be added to your project.
npx @chakra-ui/cli snippet add rich-text-editor
To get started with the core editor features, install the Tiptap StarterKit.
npm i @tiptap/starter-kit
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:
@tiptap/extension-subscript@tiptap/extension-superscript@tiptap/extension-text-align@tiptap/extension-text-stylenpm i @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-text-align @tiptap/extension-text-style
::::
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>
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.
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())
},
})
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...",
}),
],
})
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",
}),
],
})
Use the editor's getHTML() method to retrieve content and display it in a
read-only panel.
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.
Use the BubbleMenu component from Tiptap with any existing controls. The menu
will appear above any text selection, providing contextual formatting options.
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.
To add interactive task lists, use the
@tiptap/extension-task-item
and
@tiptap/extension-task-list
extensions and configure the nested property.
Add syntax-highlighted code blocks using
@tiptap/extension-code-block-lowlight
and lowlight to highlight your favorite languages.
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.
<ExampleTabs name="rich-text-editor/rich-text-editor-with-drag-handle" />To add images, use the @tiptap/extension-image extension. This lets you embed image URLs, upload files, or integrate a custom media service.
<ExampleTabs name="rich-text-editor/rich-text-editor-with-image" />To support hashtags in the editor, create a custom Tiptap node. This allows hashtags to be parsed, rendered, and handled as structured inline content.
<ExampleTabs name="rich-text-editor/rich-text-editor-with-hashtags" />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.
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.
Enable slash commands in your editor by creating a Tiptap extension that
triggers on /.
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.
<ExampleTabs name="rich-text-editor/rich-text-editor-composition" />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>
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>
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()
},
})