All posts
TutorialJune 24, 20203 min read

Beginners Guide to Slate, an Extensible Rich Text Editor

Most apps/sites need to accept content from users. This could be in the form of comments, contributions or other posts. An easy way to get started.


From the archive. This was written a while back and may not reflect my current thinking.

The Motivation

Most applications eventually require collecting user input. While basic Input or Textarea fields work for simple text, they fall short for capturing formatted content and complex elements like images. Rich text editors (RTEs) address these limitations.

After evaluating several RTE options compatible with React, I selected Slate as the preferred choice. This guide covers fundamental concepts for creating simple RTEs with React and Slate, with advanced topics deferred to future articles.

The Intuition

A rich text editor enables users to format and control their input while visualizing the final output. A "WYSIWYG" (what you see is what you get) experience.

All web-based RTEs rely on the contenteditable attribute, which allows browser users to edit element content directly. However, this approach has significant drawbacks due to inconsistent browser implementations and lack of standardization.

Rather than using contenteditable directly, most solutions take one of two approaches:

  • Build an API layer over contenteditable
  • Introduce a document model representing content independent of the DOM

Major applications like Gmail, WordPress, Google Docs, Notion, and Medium rely on contenteditable-based RTEs.

Example: contenteditable

<div style="padding:5px; background-color:#e2e2e2">
  Not Editable
  <p>or here</p>
</div>

<div contenteditable="true" style="padding:5px; background-color:#d2f8d2">
  Editable
  <p>and here</p>
</div>

While contenteditable appears simple initially, complexity increases dramatically with additional features. Developers have collectively invested millions of hours creating cross-browser reliable RTEs.

Limitations

All RTE libraries involve trade-offs:

  • Limited customization options
  • Framework-specific dependencies (React, Vue, TypeScript)
  • Steep learning curves despite low-level extensibility
  • Simple formatting is straightforward; complex types require significant effort

Slate balances these trade-offs effectively, offering accessibility for basic features while maintaining extensibility for advanced implementations.

Common Use Cases

RTEs power diverse applications:

  • Formatted Q&A platforms (Stack Overflow, Discourse)
  • Email templates (Mailchimp, Active Campaign)
  • Business documents (Google Docs, Dropbox Paper)
  • Email composition (Gmail, Hey)
  • Blog publishing (Medium, Ghost)
  • Collaborative notes (Notion, Slite)
  • Website builders (Carrd, Wix)
  • Contracts and proposals
  • Domain-specific content tools
  • Documentation platforms (GitHub, ReadMe)
  • Headless CMS (Strapi, GraphCMS, Sanity)
  • Project management (Basecamp, Taskade)

These services solve complex edge cases beyond this guide's scope.

Requirements

Code examples work identically in Create React App, Gatsby, and Next.js, with minor differences noted for localStorage integration.

Tutorial and Code

Installation

yarn add slate slate-history slate-react

Example 1: Simplest Slate RTE

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>
  )
}

This example provides the simplest functional Slate RTE, offering an API layer and document model superior to plain contenteditable divs.