Beginners Guide to Slate, an Extensible Rich Text Editor

Johnny AM

The Motivation

Most apps and sites will reach a point where they need to accept some kind of user input. The easiest solution is to create Input or Textarea fields to collect that information.

The Input and Textarea approaches have several limitations for an optimal user experience, including the capture of custom formatting and complex types like images. Rich text editors (RTEs) help to solve this problem. This article will help you get started with a React and Slate (RTE) setup.

After researching and testing out several Rich Text Editors (that would work well in React land), I landed on Slate as my preferred option.

We'll cover the basic concepts to create several different kinds of simple Rich Text Editors. Advanced use cases will be convered in a future post and build on what we learned here.

The Intuition

A Rich Text Editor is way to capture user input while providing options to format and control that input.

Rich Text Editors can often be considered WYSIWYG (what you see is what you get) editors, since users are able to visualize the final display of their input.

At the heart of all web-based rich text editors, is the contenteditable attribute. This attribute informs the browser to allow the content of its element (and child elements) to be editable directly in the browser.

The contenteditable approach is severely flawed (as documented hundreds of times online), but still a key part of building a Rich Text Editor. A major part of the problem is a lack of a specification and how browsers chose to implement it. Rather than relying directly on contenteditable, most open source solutions and commercial solutions have opted to abstract this in a couple ways.

  • Build a API layer over contenteditable
  • Introduce a document model to represent the content, rather than relying directly on the DOM.

Many projects utilize a rich text editor built on contenteditable. Some of these solutions are proprietary and others are open source. Here is a short list of products or product categories that likely use rich text editors built on top of contenteditable. Gmail, Wordpress, Google Docs, Notion, Medium, Dropbox Paper, CMS content creators etc...

Let's take the following example. Notice the first <div> tag doesn't have a content editable, but the second <div contenteditable="true"> does have it (by default, this field is set to inherit)


<div style={{padding:5, backgroundColor:'#e2e2e2'}}>
  Not Editable
  <p>or here</p>
</div>

<div contenteditable='true' style={{padding:5, backgroundColor:'#d2f8d2'}}>
  Editable
  <p>and here</p>
</div>
  

Interact with green contextEditable <div> below

Not Editable

or here

Editable

and here

Working with contenteditable seems simple, however things get very complicated as we add features. Over the years, millions of collective developer hours have been spent trying to wrangle approaches to make Rich Text Editors work accross browsers in a reliable way.

This diagram shows a high-level conceptual view of most approaches to Rich Text Editors.

Limitations

All Rich Text Editors components/libraries have trade-offs. Here are some examples.

  • Very controlled and not-customizable (for extended actions, formatting or visual)
  • Tied closely to a specific framework (i.e. react, vue, typescript)
  • Low level enough to extend, but big learning curve for even simple tasks
  • Accomplishing simple block and inline style easy, but complex types require a lot of work

Slate provides a good balance on the trade-offs. It's relatively easy to implement simple features and customize. More complex features all seem possible, however will require more learning and code.

Common use cases?

Rich Text Editors are everywhere. What you learn here (and a future advanced guide) will form the basis to start building

  • Rich formatted questions and answers (i.e. Stack Overflow, Discourse)
  • Creating email templates/newsletters (i.e Mailchimp, Active Campaign)
  • Creating business documents (i.e. Google Docs, Dropbox paper)
  • Composing emails (i.e. Gmail, Hey)
  • Writing blog posts (i.e. Medium, Ghost)
  • Collaborate notes (i.e. Notion, Slite)
  • Webpage builder (i.e. Carrd, WIX)
  • Proposal / Contract Builders (i.e. Clause, Proposify)
  • Domain specific content creation (i.e. ChatterBug, Guilded)
  • Documentation (i.e. Gitbug, ReadMe)
  • Headless CMS (i.e. Strapi, GraphCMS, Sanity, NetlifyCMS)
  • Project Management (i.e. Basecamp, Taskade)

To be clear, what I'll show here won't let you build the services above. Those services are very refined and solve complex edge cases that I won't cover here.

Requirements

These examples will work exactly the same in standard react (create react app), Gatsby and Next.js. When we cover saving to localStorage there will be a small difference between them. This will be called out as needed.

Tutorial and Code

Create new project with your favorite react library (create react app, gatsby or nextjs)

Install the slate dependencies.


 yarn add slate slate-history slate-react 
  

Example 1: The Simplest Slate RTE

Create a new page (or new component) with the following code. This will create the simplest version of a Slate Rich Text Editor that I can achieve. It isn't doing much more than the <div> with contentEditable we defined previously. The difference here is that now we have an API over contentEditable and a document model to store the contents.

I wrapped the editor in a div and added inline styles so it's easy to see, but feel free to use your favorite css-in-js library or regular css library.


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

export default function demo1() {
  const initialState = [{type: 'paragraph', children: [{ text: "edit me"}]}]
  const editor = useMemo( () => withReact(createEditor()), [])
  const [value, setValue] = useState(initialState)

  return (
    <div style={{border:'1px solid gray',borderRadius:'10px',padding: 12}}>

      <Slate editor={editor} value={value} onChange={newVal => setValue(newVal)}>
        <Editable />
      </Slate>

    </div>
  )
}
  

See the Live working demo below.

edit me

Example 1: Handle Input to the RTE

Next, let's handle some input. coming soon....

This Article is in Draft / Incomplete Status

This article is currently being written. Expect frequent updates and completion within the next few weeks. I often release early to motivate myself to finish the articles :)

Terms of Service  |  Privacy Policy

© 2020 johnny.am. All rights reserved.