Rich text editor

Tiptap based rich text editor
Import
License

Installation

Install with yarn:

yarn add @mantine/tiptap @mantine/core @mantine/hooks @tabler/icons@1.119.0 @tiptap/react @tiptap/extension-link @tiptap/starter-kit

Install with npm:

npm install @mantine/tiptap @mantine/core @mantine/hooks @tabler/icons@1.119.0 @tiptap/react @tiptap/extension-link @tiptap/starter-kit

TipTap editor

@mantine/tiptap provides a UI for Tiptap. RichTextEditor component works with Editor instance of tiptap. This means that you have full control over the editor state and configuration with useEditor hook.

In other words, RichTextEditor component does not manage state for you, controls just execute operations on the Editor instance. If you want to implement something that is related to state or component value (e.g. controlled mode, value transforms to HTML/Markdown), you should look for documentation on tiptap.dev website.

Usage

import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
const content =
'<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup>&lt;sup /&gt;</sup> and <sub>&lt;sub /&gt;</sub> tags)</li><li>Ordered and bullet lists</li><li>Text align&nbsp;</li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
function Demo() {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

Controls and extensions

Some controls require installation of additional Tiptap extensions. For example, if you want to use RichTextEditor.Underline control, you will need to install @tiptap/extension-underline package:

yarn add @tiptap/extension-underline
npm install @tiptap/extension-underline

Included with @tiptap/starter-kit (should be installed by default):

  • RichTextEditor.H1
  • RichTextEditor.H2
  • RichTextEditor.H3
  • RichTextEditor.H4
  • RichTextEditor.H5
  • RichTextEditor.H6
  • RichTextEditor.BulletList
  • RichTextEditor.OrderedList
  • RichTextEditor.Bold
  • RichTextEditor.Italic
  • RichTextEditor.Strikethrough
  • RichTextEditor.ClearFormatting
  • RichTextEditor.Blockquote
  • RichTextEditor.Code
  • RichTextEditor.CodeBlock
  • RichTextEditor.Hr

Controls that require @tiptap/extension-link extension:

  • RichTextEditor.Link
  • RichTextEditor.Unlink

Controls that require @tiptap/extension-text-align extension:

  • RichTextEditor.AlignLeft
  • RichTextEditor.AlignRight
  • RichTextEditor.AlignCenter
  • RichTextEditor.AlignJustify

Controls that require @tiptap/extension-color and @tiptap/extension-text-style extensions:

  • RichTextEditor.ColorPicker
  • RichTextEditor.Color
  • RichTextEditor.UnsetColor

Other controls with required extensions:

Placeholder

To use placeholder you will need to install @tiptap/extension-placeholder package:

yarn add @tiptap/extension-placeholder
npm install @tiptap/extension-placeholder
import { RichTextEditor } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, Placeholder.configure({ placeholder: 'This is placeholder' })],
content: '',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Content />
</RichTextEditor>
);
}

Link extension

@mantine/tiptap provides custom Link extension that is required to be used instead of @tiptap/extension-link in order for Ctrl + K keyboard shortcut to work:

// Use Link extension exported from the @mantine/tiptap package
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
function Demo() {
const editor = useEditor({
extensions: [
Link,
// ... other extensions
],
});
return <RichTextEditor editor={editor}>{/* content */}</RichTextEditor>;
}

Text color

To use text color you will need to install additional packages:

yarn add @tiptap/extension-color @tiptap/extension-text-style
npm install @tiptap/extension-color @tiptap/extension-text-style

You can use the following controls to change text color:

  • RichTextEditor.ColorPicker – allows to pick colors from given predefined color swatches and with ColorPicker component
  • RichTextEditor.Color – allows to apply given color with one click
  • RichTextEditor.UnsetColor – clears color styles
import { useEditor } from '@tiptap/react';
import { IconColorPicker } from '@tabler/icons';
import { Color } from '@tiptap/extension-color';
import TextStyle from '@tiptap/extension-text-style';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, TextStyle, Color],
content: '<p>Apply some colors to this text</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ColorPicker
colors={[
'#25262b',
'#868e96',
'#fa5252',
'#e64980',
'#be4bdb',
'#7950f2',
'#4c6ef5',
'#228be6',
'#15aabf',
'#12b886',
'#40c057',
'#82c91e',
'#fab005',
'#fd7e14',
]}
/>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Control interactive={false}>
<IconColorPicker size={16} stroke={1.5} />
</RichTextEditor.Control>
<RichTextEditor.Color color="#F03E3E" />
<RichTextEditor.Color color="#7048E8" />
<RichTextEditor.Color color="#1098AD" />
<RichTextEditor.Color color="#37B24D" />
<RichTextEditor.Color color="#F59F00" />
</RichTextEditor.ControlsGroup>
<RichTextEditor.UnsetColor />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

Code highlight

To use code highlight you will need to install additional packages:

yarn add lowlight @tiptap/extension-code-block-lowlight
npm install lowlight @tiptap/extension-code-block-lowlight
import { RichTextEditor } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
import { lowlight } from 'lowlight';
import tsLanguageSyntax from 'highlight.js/lib/languages/typescript';
// register languages that your are planning to use
lowlight.registerLanguage('ts', tsLanguageSyntax);
function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
const codeExample =
escapeHtml(`// Valid braces Kata – https://www.codewars.com/kata/5277c8a221e209d3f6000b56
const pairs: Record<string, string> = {
'[': ']',
'{': '}',
'(': ')',
};
const openBraces = Object.keys(pairs);
export function validBraces(braces: string) {
const opened: string[] = [];
for (let i = 0; i < braces.length; i += 1) {
const brace = braces[i];
if (openBraces.includes(brace)) {
opened.push(brace);
continue;
}
if (pairs[opened[opened.length - 1]] !== brace) {
return false
}
opened.pop();
}
return opened.length === 0;
}`);
function Demo() {
const editor = useEditor({
extensions: [
StarterKit,
CodeBlockLowlight.configure({
lowlight,
}),
],
content: `<p>Regular paragraph</p><pre><code>${codeExample}</code></pre>`,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.ControlsGroup>
<RichTextEditor.CodeBlock />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

Code highlight styles

You can change code highlight styles using Styles API:

Typography styles

By default, RichTextEditor renders content with TypographyStylesProvider and some additional styles. You can disable these styles by setting withTypographyStyles={false}:

import { RichTextEditor } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
function Demo() {
const editor = useEditor({
extensions: [
// ... your extensions
],
});
return (
<RichTextEditor editor={editor} withTypographyStyles={false}>
{/* content */}
</RichTextEditor>
);
}

Then you will be able to add your own styles either with global styles or with Styles API:

// ...
<RichTextEditor
editor={editor}
withTypographyStyles={false}
styles={{
content: {
'& p': {
color: 'silver',
},
},
}}
>
{/* content */}
</RichTextEditor>

Bubble menu

You can use BubbleMenu component with any RichTextEditor controls. Bubble menu will appear near a selection of text:

import { useEditor, BubbleMenu } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor, Link } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, Link],
content: '<p>Select some text to see bubble menu</p>',
});
return (
<RichTextEditor editor={editor}>
{editor && (
<BubbleMenu editor={editor}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Link />
</RichTextEditor.ControlsGroup>
</BubbleMenu>
)}
<RichTextEditor.Content />
</RichTextEditor>
);
}

Floating menu

You can use FloatingMenu component with any RichTextEditor controls. Floating menu will appear in an empty line:

import { useEditor, FloatingMenu } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor, Link } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, Link],
content: '<p>Enter a new line to see floating menu</p>',
});
return (
<RichTextEditor editor={editor}>
{editor && (
<FloatingMenu editor={editor}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.BulletList />
</RichTextEditor.ControlsGroup>
</FloatingMenu>
)}
<RichTextEditor.Content />
</RichTextEditor>
);
}

Sticky toolbar

Set sticky prop on RichTextEditor.Toolbar component to make toolbar sticky, control top property with stickyOffset. For example, on mantine.dev documentation website there is a header with 60px height, in this case we will need to set stickyOffset={60} to make sticky position correctly with fixed positioned element.

Editor context

Use useRichTextEditorContext hook to get Editor from the context. This hook can be used to create custom control or run any operations supported by the Tiptap editor API.

import { useRichTextEditorContext } from '@mantine/tiptap';
import { Button } from '@mantine/core';
function Demo() {
const { editor } = useRichTextEditorContext();
return <Button onClick={() => editor?.chain().focus().toggleBold().run()}>Make bold</Button>;
}

Custom controls

Use RichTextEditor.Control component to create custom controls. It supports all props supported by button element and has active prop to indicate active state. Note that you will need to set aria-label attribute to make control visible for screen readers.

import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor, useRichTextEditorContext } from '@mantine/tiptap';
import { IconStar } from '@tabler/icons';
function InsertStarControl() {
const { editor } = useRichTextEditorContext();
return (
<RichTextEditor.Control
onClick={() => editor?.commands.insertContent('⭐')}
aria-label="Insert star emoji"
title="Insert star emoji"
>
<IconStar stroke={1.5} size={16} />
</RichTextEditor.Control>
);
}
function Demo() {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Click control to insert star emoji</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<InsertStarControl />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

Change icons

You can change icon of control by setting icon prop. It accepts a component that must handle size prop:

import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
import { IconBold, IconItalic } from '@tabler/icons';
const BoldIcon = () => <IconBold size={18} stroke={3.5} />;
const ItalicIcon = () => <IconItalic size={18} stroke={3.5} />;
function Demo() {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Customize icons with icon prop</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold icon={BoldIcon} />
<RichTextEditor.Italic icon={ItalicIcon} />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

Labels and localization

RichTextEditor supports changing labels for all controls with labels prop:

import { RichTextEditor } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
function Demo() {
const editor = useEditor({
extensions: [
// ... your extensions
],
});
return (
<RichTextEditor
editor={editor}
labels={{
boldControlLabel: 'Make text bold',
italicControlLabel: 'Make text bold',
// ...other labels
}}
>
{/* content */}
</RichTextEditor>
);
}

Most labels are used to add aria-label and title attributes to controls, some of the labels can be a function that returns string. If you do not provide all labels, then they will be merged with the default labels.

All available labels:

// RichTextEditorLabels type can be imported from @mantine/tiptap package
export interface RichTextEditorLabels {
/** RichTextEditor.Bold control aria-label */
boldControlLabel: string;
/** RichTextEditor.Hr control aria-label */
hrControlLabel: string;
/** RichTextEditor.Italic control aria-label */
italicControlLabel: string;
/** RichTextEditor.Underline control aria-label */
underlineControlLabel: string;
/** RichTextEditor.Strike control aria-label */
strikeControlLabel: string;
/** RichTextEditor.ClearFormatting control aria-label */
clearFormattingControlLabel: string;
/** RichTextEditor.Link control aria-label */
linkControlLabel: string;
/** RichTextEditor.Unlink control aria-label */
unlinkControlLabel: string;
/** RichTextEditor.BulletList control aria-label */
bulletListControlLabel: string;
/** RichTextEditor.OrderedList control aria-label */
orderedListControlLabel: string;
/** RichTextEditor.H1 control aria-label */
h1ControlLabel: string;
/** RichTextEditor.H2 control aria-label */
h2ControlLabel: string;
/** RichTextEditor.H3 control aria-label */
h3ControlLabel: string;
/** RichTextEditor.H4 control aria-label */
h4ControlLabel: string;
/** RichTextEditor.H5 control aria-label */
h5ControlLabel: string;
/** RichTextEditor.H6 control aria-label */
h6ControlLabel: string;
/** RichTextEditor.Blockquote control aria-label */
blockquoteControlLabel: string;
/** RichTextEditor.AlignLeft control aria-label */
alignLeftControlLabel: string;
/** RichTextEditor.AlignCenter control aria-label */
alignCenterControlLabel: string;
/** RichTextEditor.AlignRight control aria-label */
alignRightControlLabel: string;
/** RichTextEditor.AlignJustify control aria-label */
alignJustifyControlLabel: string;
/** RichTextEditor.Code control aria-label */
codeControlLabel: string;
/** RichTextEditor.CodeBlock control aria-label */
codeBlockControlLabel: string;
/** RichTextEditor.Subscript control aria-label */
subscriptControlLabel: string;
/** RichTextEditor.Superscript control aria-label */
superscriptControlLabel: string;
/** RichTextEditor.ColorPicker control aria-label */
colorPickerControlLabel: string;
/** RichTextEditor.UnsetColor control aria-label */
unsetColorControlLabel: string;
/** RichTextEditor.Highlight control aria-label */
highlightControlLabel: string;
/** A function go get RichTextEditor.Color control aria-label based on color that control applies */
colorControlLabel(color: string): string;
/** aria-label for link editor url input */
linkEditorInputLabel: string;
/** placeholder for link editor url input */
linkEditorInputPlaceholder: string;
/** Content of external button tooltip in link editor when the link was chosen to open in a new tab */
linkEditorExternalLink: string;
/** Content of external button tooltip in link editor when the link was chosen to open in the same tab */
linkEditorInternalLink: string;
/** Save button content in link editor */
linkEditorSave: string;
/** Cancel button title text in color picker control */
colorPickerCancel: string;
/** Clear button title text in color picker control */
colorPickerClear: string;
/** Color picker button title text in color picker control */
colorPickerColorPicker: string;
/** Palette button title text in color picker control */
colorPickerPalette: string;
/** Save button title text in color picker control */
colorPickerSave: string;
/** aria-label for color palette colors */
colorPickerColorLabel(color: string): string;
}

Default labels (can be imported from @mantine/tiptap package):

export const DEFAULT_LABELS: RichTextEditorLabels = {
// Controls labels
linkControlLabel: 'Link',
colorPickerControlLabel: 'Text color',
highlightControlLabel: 'Highlight text',
colorControlLabel: (color) => `Set text color ${color}`,
boldControlLabel: 'Bold',
italicControlLabel: 'Italic',
underlineControlLabel: 'Underline',
strikeControlLabel: 'Strikethrough',
clearFormattingControlLabel: 'Clear formatting',
unlinkControlLabel: 'Remove link',
bulletListControlLabel: 'Bullet list',
orderedListControlLabel: 'Ordered list',
h1ControlLabel: 'Heading 1',
h2ControlLabel: 'Heading 2',
h3ControlLabel: 'Heading 3',
h4ControlLabel: 'Heading 4',
h5ControlLabel: 'Heading 5',
h6ControlLabel: 'Heading 6',
blockquoteControlLabel: 'Blockquote',
alignLeftControlLabel: 'Align text: left',
alignCenterControlLabel: 'Align text: center',
alignRightControlLabel: 'Align text: right',
alignJustifyControlLabel: 'Align text: justify',
codeControlLabel: 'Code',
codeBlockControlLabel: 'Code block',
subscriptControlLabel: 'Subscript',
superscriptControlLabel: 'Superscript',
unsetColorControlLabel: 'Unset color',
hrControlLabel: 'Horizontal line',
// Link editor
linkEditorInputLabel: 'Enter URL',
linkEditorInputPlaceholder: 'https://example.com/',
linkEditorExternalLink: 'Open link in a new tab',
linkEditorInternalLink: 'Open link in the same tab',
linkEditorSave: 'Save',
// Color picker control
colorPickerCancel: 'Cancel',
colorPickerClear: 'Clear color',
colorPickerColorPicker: 'Color picker',
colorPickerPalette: 'Color palette',
colorPickerSave: 'Save',
colorPickerColorLabel: (color) => `Set text color ${color}`,
};

RichTextEditor component props

NameTypeDescription
children *
ReactNode
Child editor components
editor *
Editor
Tiptap editor instance
labels
Partial<RichTextEditorLabels>
Labels that are used in controls
withCodeHighlightStyles
boolean
Determines whether code highlight styles should be added, true by default
withTypographyStyles
boolean
Determines whether typography styles should be added, true by default

RichTextEditor component Styles API

NameStatic selectorDescription
root.mantine-RichTextEditor-rootRoot element
toolbar.mantine-RichTextEditor-toolbarToolbar element
content.mantine-RichTextEditor-contentContent area
typographyStylesProvider.mantine-RichTextEditor-typographyStylesProviderTypographyStylesProvider component, wraps content
control.mantine-RichTextEditor-controlRichTextEditor.Control root element, used as a base for all controls
controlsGroup.mantine-RichTextEditor-controlsGroupRichTextEditor.ControlsGroup component root
linkEditor.mantine-RichTextEditor-linkEditorLink editor root element
linkEditorSave.mantine-RichTextEditor-linkEditorSaveLink editor save button
linkEditorInput.mantine-RichTextEditor-linkEditorInputLink editor url input
linkEditorExternalControl.mantine-RichTextEditor-linkEditorExternalControlLink editor external button