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.