Rich text editor

Quill based rich text editor
Import

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 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} />;
}

Configure toolbar

RichTextEditor supports these controls in toolbar:

  • bold, strike, italic, underline – general inline formatting
  • clean – removes all inline formatting
  • h1, h2, ..., h6 – headings, in default toolbar only h1-h4 headings are displayed
  • link – link editor
  • blockquote – blockquote
  • sub, sup – super and sub scripts
  • video, image – video and image embeds
  • unorderedList, orderedList – ul 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
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
  • stickyOffset – top 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 formats that user case use, set formats prop. Note that you will also need to remove toolbar buttons. In the following example three formats are enabled: bold, italic and underline, toolbar includes italic and underline controls, bold format can be added with Ctrl + B keyboard shortcut, other formats are not available:

import { useState } from 'react';
import { RichTextEditor } from '@mantine/rte';
function Demo() {
const [value, onChange] = useState('<p>Rich text editor content</p>');
return (
<RichTextEditor
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 with uploaded image url
// After promise is resolved blurred image placeholder with be replaced with uploaded
// 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} />;
}

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, add 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
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 readonly state, user cannot edit content, toolbar is hidden:

<RichTextEditor readOnly {...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} {...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 />;
}

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.

Download more icon variants from https://tabler-icons.io/i/search

RichTextEditor component props

NameTypeDescription
controls
("blockquote" | "code" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "link" | "sub" | "sup" | "video" | "image" | "bold" | "strike" | "underline" | "italic" | "clean" | "codeBlock" | ... 4 more ... | "alignRight")[][]
Toolbar controls divided into groups
formats
string[]
List of formats that should be supported by the editor
labels
RichTextEditorLabels
Labels used in toolbar button titles and assets insertion popovers
mentions
Record<string, any>
Quill mentions plugin setting
modules
Record<string, any>
Extra modules for react-quill
onChange *
(value: string, delta: Delta, sources: Sources, editor: UnprivilegedEditor) => void
Called each time value changes
onImageUpload
(image: File) => Promise<string>
Called when image image is inserted in editor
radius
number | "xs" | "sm" | "md" | "lg" | "xl"
Radius from theme.radius, or number to set border-radius in px
readOnly
boolean
Make quill editor read only
sticky
boolean
Make toolbar sticky
stickyOffset
string | number
Top toolbar position in any valid css value
value *
string | Delta
HTML content, value not forced as quill works in uncontrolled mode

RichTextEditor component Styles API

NameStatic selectorDescription
root.mantine-RichTextEditor-rootRoot element
toolbar.mantine-RichTextEditor-toolbarToolbar element
toolbarInner.mantine-RichTextEditor-toolbarInnerToolbar inner wrapper, includes everything inside toolbar
toolbarGroup.mantine-RichTextEditor-toolbarGroupToolbar controls group
toolbarControl.mantine-RichTextEditor-toolbarControlToolbar control