👀 See Sanity in action: Watch product demo now →
March 01, 2021

Top 5 rich-text React components

By William Ugonna Imoh

Content is at the heart of web interfaces. Rich-text makes up the crust of creating text content with differently formatted parts.

In this article, we will illustrate five rich text components for creating content in React applications.

We will also look at the pros and cons of each component. 

Lastly, we will discuss the future of the dynamic presentation of content using Portable Text and how to use Portable Text from Sanity Studio.

Content is at the heart of web interfaces. Rich-text makes up the crust of creating text content with differently formatted parts.

Rich text is used in various forms of presentations on the web, including blog posts, articles, listings, and more robust interfaces like e-commerce product descriptions and social media. 

Below are 5 top rich text components for React.js.


Draft.js is a robust, extensible, and customizable React.js framework for building rich text editors. It provides the building blocks for building rich text inputs with an immutable approach to managing data.

Draft.js follows the same paradigm as controlled components in React and provides an Editor component that renders a rich text input.

Draft.js exposes an EditorState API to handle/store state updates in the Editor component. 


Draft-js requires React and React-DOM. We install them using npm or yarn with:

npm install draft-js react react-dom
yarn add draft-js react react-dom


import {Editor, EditorState} from 'draft-js'

const RichtextEditor = () => {
  const [editorState, setEditorState] = useState(()=> EditorState.createEmpty())
  return (
      <Editor editorState={editorState} onChange={setEditorState}/>

The onChange handler overwrites the editorState data with the new data in the editor. editorState holds an immutable Record with all changes and events in the editor and is simply a snapshot of its state.

Draft.js provides props to manage various configurations, including editor styling on event triggers and block styling for singular rich text entities like headers and blockquotes.

With the content created in the editor, we want to convert this to HTML, which we can display on a page. There are libraries to handle the conversion of this data, including draft-convert and draftjs-to-html.


  • Robust and customizable data on a document level and lower level into blocks of various text elements
  • Uses immutable-js to manage performant state updates
  • Supports custom controls
  • Ships text directions for RTL languages and spell-checker
  • EditorState contains undo/redo stacks and any other action done on the editor


  • Requires setup from scratch, plus controls to set up a full-fledged editor
  • Requires a parser installed to render entered markup


React-draft-wysiwyg is an editor built on Draft.js. Suppose you don’t want the hassles of building your own rich text editor UI from scratch. In that case, React-Draft-WYSIWYG offers a fully fitted editor with options to customize the editor even further.

React-Draft-WYSIWYG also provides the ability to use the editor as a controlled or uncontrolled component. React-Draft-WYSIWYG provides the option to customize the toolbar options and add custom React components to them.


React-Draft-WYSIWYG depends on Draft.js, React, and React-dom. We install React-Draft-WYSIWYG using npm or yarn with:

npm install react-draft-wysiwyg draft-js react react-dom
yarn add react-draft-wysiwyg draft-js react react-dom


With React-Draft-WYSIWYG, EditorState, an immutable record of the editor’s state, is imported from draft-js and Editor from React-Draft-WYSIWYG.

Here’s usage on a React page:

import React, { useEffect, useState } from "react";
import { Editor } from "react-draft-wysiwyg";
import { EditorState } from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
export default function App() {
  const [editorState, setEditorState] = useState(() =>
  useEffect(() => {
  }, [editorState]);
  return (
      <h1>React Editors</h1>
      <h2>Start editing to see some magic happen!</h2>
      <div style={{ border: "1px solid black", padding: '2px', minHeight: '400px' }}>

The resulting editor looks like this:

The above code snippet shows the usage of React-Draft-WYSIWYG as a controlled component. In an uncontrolled behavior, the initialEditorState prop is used instead of the editorState prop in <Editor/>.


  • Provides ready-made UI out of the box
  • Allows for UI enhancements and customizations, including emoji support
  • Accepts props of CSS class names for speedy editor styling
  • Easily set up hashtags and mentions with suggestions from a dataset


  • Requires a parser to convert EditorState to HTML or any other markup.
  • Parsers for draft-js to HTML or any additional markup could be inadequate handling the different block/element types.

React Quill

Quill.js is a fast and lightweight rich text editor built with cross-platform and cross-browser support in mind.

Its strength also lies in its extensibility and configurability using themes.

React-Quill is a Quill component for React with support for TypeScript. React-Quill ships with a full-featured editor with the option to customize the toolbars and set up themes.

React-Quill is seamless to integrate. React-quill touts a hybrid input of controlled and uncontrolled behavior, using the component’s value prop bound to its state.


React-Quill also features an uncontrolled mode that uses defaultValue to instantiate the editor data.

A theme specification and a function passed to the onChange prop of the component are only required to render the editor and handle data input.

React-Quill outputs HTML and can be used in JSX elements with dangerouslySetInnerHTML.


React-quill is installed via npm or yarn with:

npm install react-quill
yarn add react-quill


Import the React-quill component along with the theme required. The default theme Snow is used when a theme is not specified.

import ReactQuill from "react-quill"
import 'react-quill/dist/quill.snow.css'

export default function App() {
  const [convertedText, setConvertedText] = useState("Some default content");
  return (
        style={{minHeight: '300px'}}


  • Allows complete toolbar customization with HTML & JSX elements support
  • Ease of setup and use
  • It outputs HTML, so it serves simpler use cases like blog posts and content presentation layers with precise data requirements.
  • Theming support for preset editor styling


  • Limited customization of content blocks
  • Security vulnerabilities of primarily rendering HTML


Slate.js, currently in beta, is a framework for building robust, rich text editors. Slate is made to be highly extensible, thus improving its native capabilities to create rich text editors. Slate is built with inspiration from tools including Quill and Draft.js.

Slate poses to solve several bottlenecks with managing rich text content, some we have seen previously in this post.

Slate aims to solve these challenges:

  • Serialization to HTML & Markdown doesn’t ship out of the box
  • Collaborative content creation is an afterthought
  • Restrictive schema definition of document models
  • Dynamic content creation should transcend beyond text, links, and media content


Slate is distributed as a monorepo and can be installed along with its React plugin using npm or yarn with:

npm install slate slate-react
yarn add slate slate-react

Slate also requires the installation of React and React-dom as peer dependencies.


A Slate editor’s essential representation is a fundamental contentEditable element, customized further until the desired functionality is achieved.

To use Slate, we import the slate editor composer and components from its React plugin.

import React, { useState, useMemo } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";

In the imports we have:

Slate: A context provider component for the Slate editor. It’s a controlled component that tracks the full editor state and updates.

Editable: Renders an editable rich text document, similar to contentEditable.

withReact: Provides the editor with React specific functionalities

Creating an <SlateEditor/> component and rendering a simple editor, we have:

import React, { useState, useMemo } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";
import "./styles.css";

export default function SlateEditor() {
  const editor = useMemo(() => withReact(createEditor()), []);
  const [value, setValue] = useState([
      type: "paragraph",
      children: [{ text: "We have some base content." }]
  return (
    <div className="App">
      <h1>React Editors</h >
      <h2>Start editing to see Slate in action!</h2>
        onChange={(newValue) => setValue(newValue)}
        <Editable style={{ border: "1px solid black" }}/>

useMemo hook maintains a consistent editor object during the component update. We initialized the Slate controlled component’s state data with an array containing an object with a block and children.

Slate uses the default renderer, which outputs a div to render the default paragraph content. The editor can be extended further using events, custom renderers, custom elements, and commands to include controls, filters, and much more.

You can find out more on using Slate to build a fully-featured rich text editor similar to Medium and dropbox paper here.


  • Ships robust APIs and handlers to build out fully featured rich text editors
  • Dynamic content blocks with types to further customize or abstract portions of the content
  • Outputs plain JSON; hence serialization to other data formats is seamless
  • Extensible with the use of plugins


  • Requires setup with a steep learning curve to handle simple content use cases
  • Requires UI setup to operate controls in the editor


Jodit is an open-source WYSIWYG editor written in TypeScript. Jodit-react, a wrapper for Jodit, is a great WYSIWYG rich text editor that ships with controls to handle most rich text formatting, links, and tables.


Install Jodit and jodit-react using npm and yarn with:

npm install jodit jodit-react
yarn add jodit jodit-react


Here’s the sample usage below to render a rich text editor with default controls and a handler to update the component state using the onBlur event.

import React, { useState, useRef } from "react";
import JoditEditor from "jodit-react";
import "./styles.css";

export default function App() {
  const editor = useRef(null);
  const [content, setContent] = useState("Start writing");
  const config = {
    readonly: false,
    height: 400
  const handleUpdate = (event) => {
    const editorContent = event.target.innerHTML;

  return (
    <div className="App">
      <h1>React Editors</h1>
      <h2>Start editing to see some magic happen!</h2>
        onChange={(newContent) => {}}
      <div dangerouslySetInnerHTML={{ __html: content }} />

We imported the required modules and set up a basic config for the editor. You can find more editor config options here.

We proceed to create a function to handle state updates with data from the editor. <JoditEditor/> renders the editor, which looks like this:


  • Provides themes and custom theme creation
  • Easy to setup WYSIWYG editor for simple content requirements
  • Provides custom controls and buttons to enhance the editor
  • Allows the creation of custom plugins for editor extensibility


  • Absence of block content with types for more in-depth content control
  • It doesn’t support robust data requirements like embeds and collaborative content creation
  • The content output is in HTML as typical with WYSIWYG editors, with potential security vulnerabilities when rendering the data using dangerouslySetInnerHTML in React.

Portable Text

Portable text is a JSON-based open specification with a renewed approach to handling and presenting rich text in modern applications. Portable text is created to solve challenges in creating rich content and its presentation in various differing interfaces.

Portable text content can be serialized into any content format. Its customizable and extensible data structure serves a limitless approach to constructing multi-level content either with data entities as siblings or children.

Portable text returns content in the form of an array containing blocks of child content with a style, types, and mark definitions - these are formats applied to content types. The JSON formatted portable text content is further transformed into any standard data format, including HTML and Markdown with serializers.

Sanity Studio

Sanity Studio is an open-source CMS with real-time collaboration on modern data requirements. Sanity utilizes portable text to serve block content created in the Sanity Studio. Portable text and the structured content approach of the Sanity Studio allow users to create various content models bordered on solving both domain and generalized content problems.

Sanity Studio also offers the ability to extend content solutions with plugins, integrations, and interfaces.

Sanity Studio installation

Sanity has multiple official starters to get started on a project quickly. These include starters for JAMStack frameworks like Gatsby.js, Next.js, Eleventy, and Gridsome. There are starters for Blogs, E-commerce, Portfolio website, and a landing page with data from Sanity Studio. We can find all starters here and even community starters.

Alternatively, we can create a new project from scratch using sanity init.

To do this, install sanity CLI globally with:

npm install -g @sanity/cli

On sanity CLI installation completion, proceed to create a Sanity account or login from the CLI with:

sanity login

Once we log in, we run sanity init, follow the CLI prompts to create a new project. We’ll choose the default dataset configuration and any of the project templates. Here, we choose the blog template that ships with the schema.

With the project’s successful setup, we change the directory into the project folder, and run sanity manage to open the project in the browser, and it looks like this:

To open the studio locally, in the project directory, we run:

sanity start

This command creates a local development server on http://localhost:3333. The local studio looks like this with the blog data schema:

In the studio’s Post menu, we click the plus (+) icon on the top right corner to open the blog creation page. The blog editor contains a Portable Text rich-text editor for structured block content. We create a sample blog content with title and text content.

We’ll deploy a GraphQL API for the studio. This way, we can query data from the studio. We’ll do this with:

sanity graphql deploy

A GraphQL API is created and deployed to sanity with a default data tag. We’ll click the presented URL to see the schema in the GraphQL playground. Here’s a sample query to fetch the title and JSON Portable text content in bodyRaw of all blog posts:

Sanity Studio Content in Gatsby.js

Gatsby.js is a tool for building super-fast single-page JAMstack applications. To use data from Sanity Studio in a gatsby project, we require a source plugin for Gatsby.js. gatsby-source-sanity solves this.

We’ll install it with:

npm install gatsby-source-sanity

In a gatsby project (different from the Sanity Studio project), we specify the plugin configuration in the plugins array of gatsby-config.js with:

module.exports = {
  plugins: [
      resolve: "gatsby-source-sanity",
      options: {
        projectId: "enter your project id",
        dataset: "production || any other dataset on sanity",
        token: "enter your sanity read token",

Refresh the gatsby development server and open the graphql playground to see the source data from sanity. 

We can pull the content we want from sanity into our gatsby project, along with other content created on Sanity. 

In Gatsby projects, we use block-content-to-react to serialize Portable Text.


In this post, we discussed five popular React rich text editors. We discussed robust editors with block content to WYSIWYG editors for simple content requirements. Each of these is fit for specific use cases depending on the complexity of the project.

We discussed portable text and the problems it solves in dynamic content creation - lastly, we set up Sanity Studio with a blog schema that uses Portable Text. We created a GraphQL API for the content and utilized gatsby-source-sanity to source the GraphQL data into a Gatsby.js project.

Sanity.io – build remarkable experiences at scale

Sanity.io is a platform to build websites and applications. It comes with great APIs that let you treat content like data. Give your team exactly what they need to edit and publish their content with the customizable Sanity Studio. Get real-time collaboration out of the box. Sanity.io comes with a hosted datastore for JSON documents, query languages like GROQ and GraphQL, CDNs, on-demand asset transformations, presentation agnostic rich text, plugins, and much more.

Don't compromise on developer experience. Join thousands of developers and trusted companies and power your content with Sanity.io. Free to get started, pay-as-you-go on all plans.