Rich text editor

Quill based rich text editor
Import
License

Installation

Package depends on @mantine/core and @mantine/hooks.

Install with yarn:

yarn add @mantine/rte

Install with npm:

npm install @mantine/rte

Demo

Usage

value and onChange props are required for component to work. Note that even though component is controlled, you cannot force value (limitation of Quill.js library).

import { useState } from 'react';
import { RichTextEditor } from '@mantine/rte';
const initialValue =
'<p>Your initial <b>html value</b> or an empty string to init editor without value</p>';
function Demo() {
const [value, onChange] = useState(initialValue);
return <RichTextEditor value={value} onChange={onChange} id="rte" />;
}

Configure toolbar

RichTextEditor supports these controls in toolbar:

  • bold, strike, italic, underline – general inline formatting.
  • clean – removes all inline formatting.
  • h1, h2, ..., h6 – headings. Only h1-h4 headings are displayed in the toolbar by default.
  • link – link editor.
  • blockquote – blockquote.
  • sub, sup – super and sub scripts.
  • video, image – video and image embeds.
  • unorderedList, orderedListul and ol tags.
  • alignCenter, alignLeft, alignRight – controls text-align.
  • code and codeBlock – inline and block code.

You can add, remove and configure controls arrangement in toolbar with controls prop:

Default toolbar:
Custom toolbar:
<RichTextEditor
id="rte"
controls={[
['bold', 'italic', 'underline', 'link', 'image'],
['unorderedList', 'h1', 'h2', 'h3'],
['sup', 'sub'],
['alignLeft', 'alignCenter', 'alignRight'],
]}
/>

To configure sticky toolbar properties set following props:

  • sticky – set to false to make toolbar stay at the top.
  • stickyOffsettop property, used with sticky position. Use it to offset elements with fixed position. For example, Mantine docs website has 60px header. In this case you should set stickyOffset to 60.
// Toolbar stays at the top
<RichTextEditor sticky={false} />
// Toolbar position is set to sticky with top: 40px
<RichTextEditor stickyOffset={40} />

Images and videos embeds

Restrict formats

To restrict the allowed formats, set formats prop with an array of quill formats. Note that you will also need to remove toolbar buttons. In the following example three formats are enabled: bold, italic and underline while toolbar includes italic and underline controls. bold format can be added with Ctrl + B keyboard shortcut. Other formats are disabled:

import { useState } from 'react';
import { RichTextEditor } from '@mantine/rte';
function Demo() {
const [value, onChange] = useState('<p>Rich text editor content</p>');
return (
<RichTextEditor
id="rte"
value={value}
onChange={onChange}
formats={['bold', 'italic', 'underline']}
controls={[['italic', 'underline']]}
/>
);
}

Images upload

RichTextEditor will handle images upload in following situations:

  • Image button click in toolbar
  • Image was pasted from clipboard into editor
  • Image was dropped into editor

To set up images upload add onImageUpload function:

import { useState, useCallback } from 'react';
import { RichTextEditor } from '@mantine/rte';
function Demo() {
// Example with imgbb.com. Usually you would use similar logic to upload to S3-like storages.
// Function must return a promise that resolves to uploaded image url.
// After promise is resolved, blurred image placeholder with be replaced with the uploaded image's url.
// Note that useCallback is required.
const handleImageUpload = useCallback(
(file: File): Promise<string> =>
new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('image', file);
fetch('https://api.imgbb.com/1/upload?key=api_key', {
method: 'POST',
body: formData,
})
.then((response) => response.json())
.then((result) => resolve(result.data.url))
.catch(() => reject(new Error('Upload failed')));
}),
[]
);
const [value, onChange] = useState('');
return (
<RichTextEditor value={value} onChange={onChange} onImageUpload={handleImageUpload} id="rte" />
);
}

Important! If you do not provide onImageUpload all images will be converted to base64 format. In most cases this is not a valid option to store images so make sure you provide onImageUpload if you are planning to use images.

Mentions

RichTextEditor comes with quill-mentions plugin. To add mentions support, provide quill-mentions configuration to mentions prop:

import { useState, useMemo } from 'react';
import { RichTextEditor } from '@mantine/rte';
const people = [
{ id: 1, value: 'Bill Horsefighter' },
{ id: 2, value: 'Amanda Hijacker' },
{ id: 3, value: 'Leo Summerhalter' },
{ id: 4, value: 'Jane Sinkspitter' },
];
const tags = [
{ id: 1, value: 'JavaScript' },
{ id: 2, value: 'TypeScript' },
{ id: 3, value: 'Ruby' },
{ id: 3, value: 'Python' },
];
function Demo() {
const [value, onChange] = useState('<p>Type @ or # to see mentions autocomplete</p>');
const mentions = useMemo(
() => ({
allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
mentionDenotationChars: ['@', '#'],
source: (searchTerm, renderList, mentionChar) => {
const list = mentionChar === '@' ? people : tags;
const includesSearchTerm = list.filter((item) =>
item.value.toLowerCase().includes(searchTerm.toLowerCase())
);
renderList(includesSearchTerm);
},
}),
[]
);
return (
<RichTextEditor
id="rte"
value={value}
onChange={onChange}
placeholder="Type @ or # to see mentions autocomplete"
mentions={mentions}
/>
);
}

Extra modules

You can provide any amount of extra modules. Note that it is required to memoize modules object:

import { useMemo } from 'react';
import { RichTextEditor } from '@mantine/rte';
function Demo() {
const modules = useMemo(
() => ({
history: { delay: 2500, userOnly: true },
syntax: true,
}),
[]
);
return <RichTextEditor modules={modules} {...otherProps} />;
}

Read only

When editor is in readonly state, user cannot edit content and the toolbar is hidden:

<RichTextEditor readOnly id="rte" {...otherProps} />

Keyboard shortcuts

  • ⌘ + B / Ctrl + B – toggle bold format in current selection
  • ⌘ + I / Ctrl + I – toggle italic format in current selection
  • ⌘ + U / Ctrl + U – toggle underline format in current selection
  • ⌘ + K / Ctrl + K – add link to current selection
  • ⌘ + option + 1 / Ctrl + Alt + 1 – toggle heading at current line, valid for 1-6 headings

Get editor ref

import { useRef, useEffect } from 'react';
import { RichTextEditor, Editor } from '@mantine/rte';
function Demo() {
const editorRef = useRef<Editor>();
useEffect(() => {
editorRef.current.focus();
}, []);
return <RichTextEditor ref={editorRef} id="rte" {...otherProps} />;
}

Render HTML content

You can use TypographyStylesProvider component to render RichTextEditor value.

Server side rendering

Quill does not support server side rendering as it relies on browser API. To make component work on server you will need to create a wrapper component with additional checks.

General strategy:

// Create a separate component which will load RichTextEditor only in browser
import type { RichTextEditorProps } from '@mantine/rte';
export function RichText(props: RichTextEditorProps) {
if (typeof window !== 'undefined') {
// eslint-disable-next-line import/extensions, global-require
const { RichTextEditor } = require('@mantine/rte');
return <RichTextEditor {...props} />;
}
// Render anything as fallback on server, e.g. loader or html content without editor
return null;
}

Usage with Next.js

To make component work with Next.js use dynamic module:

// RichText.tsx in your components folder
import dynamic from 'next/dynamic';
export default dynamic(() => import('@mantine/rte'), {
// Disable during server side rendering
ssr: false,
// Render anything as fallback on server, e.g. loader or html content without editor
loading: () => null,
});

Then when you want to use RichTextEditor import your component instead:

import RichTextEditor from '../components/RichText';
function MyPage() {
return <RichTextEditor id="rte" />;
}

React strict mode limitations

RichTextEditor component is not compatible with React strict mode. You will experience the following error if strict mode is enabled: TypeError: Cannot read properties of null (reading 'index'). To fix it, disable strict mode.

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