Looking for the best React Editor library
Tags: react, reacteditorlibrary, javascript, kendoreact
Published: 2023-01-28 13:15:00


---
title: Looking for the best React Editor library
published: true
description: Rich Text Editors are a great way to provide users with capabilities to create rich content that can contain images, videos, formatted text and more. This article covers a few popular React Rich Text Editors and explores their pros and cons and how to implement them.
tags: react, reacteditorlibrary, javascript, kendoreact
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93l1bpa9z8fjip2f5cg4.png
published_at: 2023-01-28 13:15 +0000
---

Gathering user input is very common across the internet, and forms with various fields can be found on many websites. However, forms can vary in size and complexity. Simple forms can contain just one input field, while more complex ones could involve multiple steps with different fields like textareas, dropdowns, radios, checkboxes, and so on. However, there are circumstances when a user should be able to provide richer content, such as text in different formatting and sizes, images, videos, links, etc., without a limitation of pre-defined fields. That's where Rich Text Editors come into play.

As is the case with a lot of things, there are plenty of libraries that can be used to incorporate a Rich Text Editor in React. However, they all differ in what they provide and how they work. For example, some React Rich Text Editors provide barebones that can be used and built upon to create a Rich Text Editor, while others offer full-blown editors with a lot of functionality out-of-the-box that can be configured to suit the application's needs. In this article, we will explore a few popular React Rich Text Editors, the features they provide and how to implement them, specifically:

- Slate
- TipTap
- Quill
- KendoReact Rich Text Editor

You can find the full code example in [this GitHub repo](https://github.com/ThomasFindlay/looking-for-best-react-editor-library) and an interactive example in the [Stackblitz](https://stackblitz.com/edit/github-xj3uct?embed=1&file=src/App.jsx&view=preview).

If you would like to follow this article, you can clone the GitHub repo for this article and switch to the `start` branch. The commands below show how to do that:

```sh
$ git clone https://github.com/ThomasFindlay/looking-for-best-react-editor-library
$ cd looking-for-best-react-editor-library
$ git checkout start
$ npm install
$ npm run dev
```

## Slate

[Slate](https://github.com/ianstormtaylor/slate), as per its documentation, is a completely customizable framework for building rich text editors. Therefore, it doesn't offer a feature-rich text editor but instead provides tools to build one. Let's create a component called `Slate` and see what the Slate editor looks like.

**src/components/Slate.jsx**

```jsx
import { useMemo } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";

const initialValue = [
  {
    type: "paragraph",
    children: [
      {
        text: "Hello Slate Editor",
      },
    ],
  },
];

const SlateEditor = props => {
  const editor = useMemo(() => withReact(createEditor()), []);
  return (
    <div>
      <h2> Slate </h2>
      <Slate editor={editor} value={initialValue}>
        <Editable
          style={{
            border: "1px solid grey",
            padding: "0.25rem",
          }}
        />
      </Slate>
    </div>
  );
};

export default SlateEditor;
```

Next, import and render the `Slate` component in the `App` component.

**src/App.jsx**

```jsx
import "./App.css";
import Slate from "./components/Slate";

function App() {
  return (
    <div className="App">
      <h1>React Editors</h1>

      <div
        style={{
          width: 700,
          display: "flex",
          flexDirection: "column",
          gap: 32,
        }}
        >
        <Slate />
      </div>
    </div>
  );
}

export default App;
```

The initial Slate editor looks just like a normal textarea field.

![Barebones Slate Editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byycsju5utj4j5cgzi08.gif)

As mentioned before, Slate offers tools that can be used to build a Rich Text Editor. Functionality such as boldening and italicizing text or adding images is not provided and instead needs to be implemented. This isn't great if all you want is an easily configurable text editor. However, it can be useful if more custom functionality is needed. Let's have a look at how to add functionality to insert an image with Slate.

The usefulness of Slate lies in the fact that it is completely customizable, and we can define what Slate should render and how. In the initial example above, we provided the following object to display the `Hello Slate Editor` message in the editor:

```js
const initialValue = [
  {
    type: "paragraph",
    children: [
      {
        text: "Hello Slate Editor",
      },
    ],
  },
];
```

We can customise what the Slate editor renders by providing a custom component via the `renderElement` prop. The custom component can decide how to render a block in the Slate editor based on its type.  The default element type is a `paragraph`, but we will add a new one called `image`. First, let's create a button and logic that will trigger a popup to enter a URL and then insert an image into the editor. 

**src/components/slate/components/InsertImageButton.jsx**

```jsx
import { Transforms } from "slate";
import { useSlateStatic } from "slate-react";
import { isImageUrl } from "../helpers/isImageUrl";

const insertImage = (editor, url) => {
  const text = { text: "" };
  const image = { type: "image", url, children: [text] };
  Transforms.insertNodes(editor, image);
};

export const withImages = editor => {
  const { insertData, isVoid } = editor;

  editor.isVoid = element => {
    return element.type === "image" ? true : isVoid(element);
  };

  editor.insertData = data => {
    const text = data.getData("text/plain");

    const { files } = data;

    if (files && files.length > 0) {
      for (const file of files) {
        const reader = new FileReader();
        const [mime] = file.type.split("/");

        if (mime === "image") {
          reader.addEventListener("load", () => {
            const url = reader.result;
            insertImage(editor, url);
          });

          reader.readAsDataURL(file);
        }
      }
    } else if (isImageUrl(text)) {
      insertImage(editor, text);
    } else {
      insertData(data);
    }
  };

  return editor;
};

const InsertImageButton = () => {
  const editor = useSlateStatic();
  return (
    <button
      onMouseDown={event => {
        event.preventDefault();
        const url = window.prompt("Enter the URL of the image:");
        if (url && !isImageUrl(url)) {
          alert("URL is not an image");
          return;
        }
        url && insertImage(editor, url);
      }}
    >
      Add Image
    </button>
  );
};

export default InsertImageButton;
```

We have three main pieces in the code above - `insertImage` and `withImages` functions and the `InsertImageButton` component.

The first one is responsible for creating and inserting a new *image* node.  The `withImages` function is a higher-order function that  modifies the `editor` instance. Specifically, it monkey patches `isVoid` and `insertData` methods to handle the new `image` node type. The `InsertImageButton` component, as the name suggests, renders a button that prompts a user to enter a URL and then executes the `insertImage` function.

The `withImages` function utilises a helper called `isImageUrl`, but it doesn't exist yet, so let's take care of that.

**src/components/slate/helpers/isImageUrl.js**

```js
import imageExtensions from "image-extensions";
import isUrl from "is-url";

export const isImageUrl = url => {
  if (!url) return false;
  if (!isUrl(url)) return false;
  const ext = new URL(url).pathname.split(".").pop();
  return imageExtensions.includes(ext);
};
```

Next, we need to create a custom `Element` component and the `Image` component so the Slate editor knows how to handle nodes with the  `image` type.

**src/components/slate/components/slate-elements/Element.jsx** 

```jsx
import Image from "./Image";

const Element = props => {
  const { attributes, children, element } = props;
  switch (element.type) {
    case "image":
      return <Image {...props} />;
    default:
      return <p {...attributes}>{children}</p>;
  }
};

export default Element;
```

The `Element` component uses the `element.type` value to render either a paragraph element or the `Image` component that we will create now.

**src/components/slate/components/slate-elements/Image.jsx** 

```jsx
import { useFocused, useSelected } from "slate-react";
import { css } from "@emotion/css";

const Image = ({ attributes, children, element }) => {
  const selected = useSelected();
  const focused = useFocused();
  return (
    <div {...attributes}>
      {children}
      <div
        contentEditable={false}
        className={css`
          position: relative;
        `}
      >
        <img
          src={element.url}
          className={css`
            display: block;
            max-width: 100%;
            max-height: 20em;
            box-shadow: ${selected && focused ? "0 0 0 3px #B4D5FF" : "none"};
          `}
        />
      </div>
    </div>
  );
};

export default Image;
```

The `Image` component renders an image element and utilises the `selected` and `focused` values to highlight the rendered image.  

Now we can create a new component to render the Slate editor with image capabilities.

**src/components/slate/SlateWithImage.jsx**

```jsx
import { useMemo } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";
import InsertImageButton, { withImages } from "./components/InsertImageButton";
import Element from "./components/slate-elements/Element";

const initialValue = [
  {
    type: "paragraph",
    children: [
      {
        text: "Hello Slate Editor",
      },
    ],
  },
];

const SlateEditor = props => {
  const editor = useMemo(() => withImages(withReact(createEditor())), []);
  return (
    <div>
      <h2> Slate With Image </h2>
      <Slate editor={editor} value={initialValue}>
        <div style={{ marginBottom: "1rem" }}>
          <InsertImageButton />
        </div>
        <Editable
          renderElement={props => <Element {...props} />}
          style={{
            border: "1px solid grey",
            padding: "0.25rem",
          }}
        />
      </Slate>
    </div>
  );
};

export default SlateEditor;
```

Last but not least, let's render the `SlateWithImage` component.

**src/App.jsx**

```jsx
import "./App.css";
import SlateWithImage from "./components/slate/SlateWithImage";

function App() {
  return (
    <div className="App">
      <h1>React Editors</h1>
      <div
        style={{
          width: 700,
          display: "flex",
          flexDirection: "column",
          gap: 32,
        }}
        >
        <SlateWithImage />
      </div>
    </div>
  );
}

export default App;
```

The gif below shows what the Slate editor with an image looks like.

![Slate editor with image functionality](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tw0cj74tptggyggwtf60.gif)

If you're up for a challenge, you can enhance the image upload functionality and allow a user to upload an image from their device instead of only providing a URL.

## TipTap

[TipTap](https://tiptap.dev/), built on top of [ProseMirror](https://prosemirror.net/), is a headless editor framework that gives full control over every single aspect of the text editor experience. Similarly to Slate, TipTap doesn't offer a fully featured Rich Text Editor; instead, it offers a lot of extensions and can be customized to incorporate new features. Let's have a look at how we can implement a TipTap editor with the image extension that will provide similar functionality to the one we implemented in the last section for the Slate editor.

**src/components/TipTap.jsx**

```jsx
import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import style from "./TipTap.module.scss";
import { Image } from "@tiptap/extension-image";
import { useCallback } from "react";

Image.configure({});

const TipTap = props => {
  const editor = useEditor({
    extensions: [StarterKit, Image],
    content: "<p>Hello from TipTap Rich Text Editor!</p>",
  });

  const addImage = useCallback(() => {
    const url = window.prompt("URL");
    if (!url) return;
    editor.chain().focus().setImage({ src: url }).run();
  }, [editor]);

  if (!editor) return null;

  return (
    <div className={style.tiptapContainer}>
      <h2>TipTap</h2>
      <div className="k-display-flex k-gap-2">
        <button onClick={() => editor.chain().focus().toggleBold().run()}>
          B
        </button>
        <button onClick={() => editor.chain().focus().toggleItalic().run()}>
          I
        </button>
        <button onClick={addImage}>Add Image</button>
      </div>
      <EditorContent editor={editor} />
    </div>
  );
};

export default TipTap;
```

A TipTap editor instance is created using the `useEditor` hook and then passed to the `EditorContent` component. The `useEditor` hook accepts a config object as an argument. In our example, we have two extensions - [StarterKit](https://tiptap.dev/api/extensions/starter-kit) and [Image](https://tiptap.dev/api/nodes/image). The former includes several common features for formatting text and adding richer content, such as headings, lists, bullets, code blocks, etc. The Image extension provides the logic that handles the insertion of images into the Tiptap editor. Besides the `EditorContent` component that was mentioned earlier, we also have three buttons. The first two allow boldening and italicization of text, whilst the last one triggers a prompt asking for an image URL and then adds the image to the editor.

TipTap is a headless editor, so it doesn't provide any styles by default. However, TipTap's documentation has some basic styles for the editor.

**src/components/TipTap.module.scss**

```scss
/* Basic editor styles */
.tiptapContainer {
  .ProseMirror {
    padding: 0.5rem;

    > * + * {
      margin-top: 0.75em;
    }

    ul,
    ol {
      padding: 0 1rem;
    }

    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
      line-height: 1.1;
    }

    code {
      background-color: rgba(#616161, 0.1);
      color: #616161;
    }

    pre {
      background: #0d0d0d;
      color: #fff;
      font-family: "JetBrainsMono", monospace;
      padding: 0.75rem 1rem;
      border-radius: 0.5rem;

      code {
        color: inherit;
        padding: 0;
        background: none;
        font-size: 0.8rem;
      }
    }

    img {
      max-width: 100%;
      height: auto;
    }

    blockquote {
      padding-left: 1rem;
      border-left: 2px solid rgba(#0d0d0d, 0.1);
    }

    hr {
      border: none;
      border-top: 2px solid rgba(#0d0d0d, 0.1);
      margin: 2rem 0;
    }
  }
}
```

After the styles are ready, we need to render the `TipTap` component, so let's update the `App` component.

**src/App.jsx**

```jsx
import "./App.css";
import TipTap from "./components/TipTap";

function App() {
  return (
    <div className="App">
      <h1>React Editors</h1>

      <div
        style={{
          width: 700,
          display: "flex",
          flexDirection: "column",
          gap: 32,
        }}
        >
        <TipTap />
      </div>
    </div>
  );
}

export default App;
```

Below you can see what the TipTap editor looks like.

![TipTap editor with image capabilities](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0e6jwv20n7d4xc7rh1b.gif)

## Quill

[Quill.js](https://quilljs.com/) is a rich text editor that provides a configurable editor with various features. Compared to Slate and TipTap, Quill is a drop-in solution that doesn't require building common text editing functionality from scratch.

To demonstrate how Quill works, we are going to use the [React-Quill](https://www.npmjs.com/package/react-quill) package, which is a React wrapper around the Quill editor.  Below we have a component that will render the React Quill editor with a toolbar that offers tools for formatting text.

**src/components/Quill.jsx**

```jsx
import React, { useState } from "react";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";

const modules = {
  toolbar: [
    [{ header: [1, 2, false] }],
    ["bold", "italic", "underline", "strike", "blockquote"],
    [
      { list: "ordered" },
      { list: "bullet" },
      { indent: "-1" },
      { indent: "+1" },
    ],
    ["link", "image"],
    ["clean"],
  ],
};

const Quill = props => {
  const [value, setValue] = useState("");

  return (
    <div>
      <h2>Quill</h2>
      <ReactQuill
        theme="snow"
        value={value}
        onChange={setValue}
        modules={modules}
      />
    </div>
  );
};

export default Quill;
```

The `modules` prop can be used to configure the Quill editor. In this example, we configure the toolbar and specify what text editing features should be active and in which order they should be displayed. 

We need to update the `App` component to render the Quill editor.

**src/App.jsx**

```jsx
import "./App.css";
import Quill from "./components/Quill";

function App() {
  return (
    <div className="App">
      <h1>React Editors</h1>
      <div
        style={{
          width: 700,
          display: "flex",
          flexDirection: "column",
          gap: 32,
        }}
        >
        <Quill />
      </div>
    </div>
  );
}

export default App;
```

Here's what the Quill editor looks like.

![Quill React Rich Text Editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mo9tfz91bpgt1be5y6z7.gif)

## [KendoReact](https://www.telerik.com/kendo-react-ui) Editor

[KendoReact Rich Text Editor](https://www.telerik.com/kendo-react-ui/editor), similarly to Quill, provides a feature-rich editor component that offers a large number of editor tools. It can be easily configured with as little or as many text editing tools as needed. Let's see it in action.

**src/components/KendoReactEditor.jsx**

```jsx
import "@progress/kendo-theme-material/dist/all.css";
import { Editor, EditorTools } from "@progress/kendo-react-editor";

const {
  Bold,
  Italic,
  Underline,
  Strikethrough,
  Subscript,
  Superscript,
  AlignLeft,
  AlignCenter,
  AlignRight,
  AlignJustify,
  Indent,
  Outdent,
  OrderedList,
  UnorderedList,
  Undo,
  Redo,
  FontSize,
  FontName,
  FormatBlock,
  Link,
  Unlink,
  InsertImage,
  ViewHtml,
  InsertTable,
  AddRowBefore,
  AddRowAfter,
  AddColumnBefore,
  AddColumnAfter,
  DeleteRow,
  DeleteColumn,
  DeleteTable,
  MergeCells,
  SplitCell,
} = EditorTools;

const content = "Hello from KendoReact Editor";

const KendoReactEditor = props => {
  return (
    <div>
      <h2>KendoReact Editor</h2>
      <Editor
        tools={[
          [Bold, Italic, Underline, Strikethrough],
          [Subscript, Superscript],
          [AlignLeft, AlignCenter, AlignRight, AlignJustify],
          [Indent, Outdent],
          [OrderedList, UnorderedList],
          FontSize,
          FontName,
          FormatBlock,
          [Undo, Redo],
          [Link, Unlink, InsertImage, ViewHtml],
          [InsertTable],
          [AddRowBefore, AddRowAfter, AddColumnBefore, AddColumnAfter],
          [DeleteRow, DeleteColumn, DeleteTable],
          [MergeCells, SplitCell],
        ]}
        contentStyle={{ height: 320 }}
        defaultContent={content}
      />
    </div>
  );
};

export default KendoReactEditor;
```

The `EditorTools` object contains toolbar components for the rich text editor.  These are then passed to the `Editor` component via the `tools` prop. The tools can be grouped into sections and ordered using nested arrays and changing the order in which they are defined.

Finally, let's render the KendoReact Editor.

**src/App.jsx**

```jsx
import "./App.css";
import KendoReactEditor from "./components/KendoReactEditor";

function App() {
  return (
    <div className="App">
      <h1>React Editors</h1>

      <div>
        <div
          style={{
            width: 700,
            display: "flex",
            flexDirection: "column",
            gap: 32,
          }}
        >
          <KendoReactEditor />
        </div>
      </div>
    </div>
  );
}

export default App;

```

And here's what it looks like.

![KendoReact Rich Text Editor add image functionality](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znt04asqx22cwcwtppxf.gif)

KendoReact Text Editor, besides an impressive number of text editing features, also has built-in support for tables, find and replace, globalization and many other features. Here's an example of the table functionality in the editor.

![KendoReact Rich Text Editor table functionality](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jnseru43esy5kldhw3z.gif)

## Summary

There are multiple React Editor libraries available on the market that developers can choose from. However, it's important to choose an appropriate library for your project. If you want to build a custom rich text editor, Slate and TipTap could be great choices, as they can provide the necessary tools. However, if you want to easily add a rich text editor that can be configured to provide a user with a lot of options for text editing, then Quill and KendoReact Editor are the appropriate choices, as they will require much less code to achieve the same outcome. What's more, KendoReact Editor comes with another benefit, as it is a part of an enterprise-grade suite of React UI components called [KendoReact UI](https://www.telerik.com/kendo-react-ui) that offers over 100 ready-to-use React components.