Rich text editor

Quill based rich text editor


Package has peer dependencies: react, react-dom, @mantine/hooks and @mantine/core:.

Install with npm:

npm install @mantine/rte @mantine/core @mantine/hooks

Install with yarn:

yarn add @mantine/rte @mantine/core @mantine/hooks



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

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

Default toolbar:
Custom toolbar:
['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

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 } from 'react';
import { RichTextEditor } from '@mantine/rte';
// Example with, 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
const handleImageUpload = (file: File): Promise<string> =>
new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('image', file);
fetch('', {
method: 'POST',
body: formData,
.then((response) => response.json())
.then((result) => resolve(
.catch(() => reject(new Error('Upload failed')));
function Demo() {
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.

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

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 React from 'react';
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
loader: () => null,

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

import RichTextEditor from '../components/RichText';
function MyPage() {
return <RichTextEditor />;
Build fully functional accessible web applications with ease
Your feedback is most valuable contribution to the project, please share how you use Mantine, what features are missing and what is done good
Leave feedback