draft-js, quill, slate, and tiptap are libraries designed to handle rich text editing in web applications. They manage the complexity of content editable regions, cursor selection, and data serialization. draft-js is a legacy React-specific solution originally built by Meta. quill is a standalone, opinionated editor with a built-in UI. slate is a fully customizable framework for building editors in React. tiptap is a headless wrapper around ProseMirror that supports multiple frameworks like Vue and React.
Building a rich text editor from scratch is notoriously difficult due to browser inconsistencies in handling content editable regions. draft-js, quill, slate, and tiptap solve this by providing abstractions over the DOM. However, they differ significantly in architecture, flexibility, and maintenance status. Let's compare how they handle core engineering challenges.
draft-js uses an immutable model backed by React state.
// draft-js: Immutable EditorState
import { EditorState } from 'draft-js';
const state = EditorState.createEmpty();
const content = state.getCurrentContent();
quill operates on a Delta format independent of React.
// quill: Independent Delta model
import Quill from 'quill';
const quill = new Quill('#editor', { theme: 'snow' });
const delta = quill.getContents();
slate treats the editor as a controlled React component.
// slate: Controlled React components
import { createEditor } from 'slate';
const editor = createEditor();
// Rendered via <Slate><Editable /></Slate>
tiptap is a headless wrapper around ProseMirror.
// tiptap: Headless extension setup
import { useEditor } from '@tiptap/react';
const editor = useEditor({ extensions: [StarterKit] });
// Rendered via <EditorContent editor={editor} />
draft-js has a fixed schema with limited customization.
// draft-js: Custom block rendering
function blockRendererFn(contentBlock) {
if (contentBlock.getType() === 'atomic') {
return { component: MediaBlock, editable: false };
}
}
quill allows customization through modules and themes.
// quill: Registering a custom blot
import Quill from 'quill';
const Block = Quill.import('blots/block');
class CustomBlock extends Block {}
Quill.register(CustomBlock);
slate offers complete schema flexibility.
// slate: Custom node rendering
const renderElement = props => {
switch (props.element.type) {
case 'quote': return <QuoteElement {...props} />;
default: return <DefaultElement {...props} />;
}
};
tiptap uses an extension system for customization.
// tiptap: Custom extension
const Highlight = Extension.create({
name: 'highlight',
addOptions() { return { color: null }; }
});
draft-js is built exclusively for React.
// draft-js: React specific import
import { Editor } from 'draft-js';
function MyEditor() {
return <Editor editorState={state} onChange={setState} />;
}
quill is framework agnostic.
// quill: Vanilla JS initialization
const quill = new Quill('#editor', { theme: 'snow' });
// Can be wrapped in any framework component
slate is built exclusively for React.
// slate: React hooks usage
import { Slate, Editable, withReact } from 'slate-react';
function App() {
const [editor] = useState(() => withReact(createEditor()));
return <Slate editor={editor}><Editable /></Slate>;
}
tiptap supports multiple frameworks officially.
// tiptap: React specific hook
import { useEditor, EditorContent } from '@tiptap/react';
function App() {
const editor = useEditor({ extensions: [] });
return <EditorContent editor={editor} />;
}
draft-js exports to a custom raw JSON format.
// draft-js: Export to raw JSON
import { convertToRaw } from 'draft-js';
const rawContent = convertToRaw(contentState);
// Requires converter to get HTML string
quill exports to Delta JSON or HTML.
// quill: Get Delta or HTML
const delta = quill.getContents(); // JSON
const html = quill.root.innerHTML; // HTML string
slate stores data as a plain JSON tree.
// slate: Get JSON value
const value = editor.children; // Plain JSON array
// Use serialize function to convert to HTML
tiptap supports JSON and HTML out of the box.
// tiptap: Get JSON or HTML
const json = editor.getJSON(); // JSON object
const html = editor.getHTML(); // HTML string
draft-js is effectively deprecated for new work.
// draft-js: Legacy warning
// npm install draft-js
// ⚠️ Not recommended for new projects
quill is actively maintained with version 2.0 released.
// quill: Active maintenance
// npm install quill
// ✅ Stable and maintained
slate has frequent updates and a strong community.
// slate: Frequent updates
// npm install slate slate-react
// ✅ Active but watch for breaking changes
tiptap is rapidly growing with enterprise backing.
// tiptap: Rapid growth
// npm install @tiptap/react
// ✅ Highly recommended for modern stacks
| Feature | draft-js | quill | slate | tiptap |
|---|---|---|---|---|
| Status | ⚠️ Legacy | ✅ Stable | ✅ Active | ✅ Active |
| Framework | React Only | Any | React Only | React, Vue, Svelte |
| UI | Coupled | Built-in | Headless | Headless |
| Data | Custom Raw | Delta/HTML | JSON | JSON/HTML |
| Learning Curve | Medium | Low | High | Medium |
draft-js is a legacy tool 🕰️ — do not use it for new projects. It served a purpose in the past but is now outdated compared to modern solutions.
quill is a ready-to-use solution 📦 — perfect for standard needs where speed matters more than customization. Ideal for simple forms or blogs.
slate is a builder's kit 🛠️ — best for React teams who need to invent new editing behaviors. It requires more work but offers total control.
tiptap is the modern standard 🚀 — combines ProseMirror power with a developer-friendly API. Choose this for robust, future-proof applications across any framework.
Final Thought: For most new projects, tiptap offers the best balance of power and ease of use. If you need something quick and simple, quill works well. Avoid draft-js unless you are maintaining old code.
Avoid draft-js for new projects. It is in maintenance mode and lacks modern features like collaborative editing or robust mobile support. Only choose this if you are maintaining an existing legacy codebase that already depends on it.
Choose quill if you need a standard rich text editor with a built-in UI and quick setup. It is ideal for blogs, comment sections, or simple content fields where deep customization is not required.
Choose slate if you need full control over the editor schema and behavior within a React application. It is best for complex tools like document editors or note-taking apps where you need to define custom nodes and logic.
Choose tiptap for a headless approach with ProseMirror stability. It is suitable for teams using Vue, React, or Svelte who need robust collaboration features without being locked into a specific UI theme.
Draft.js is a JavaScript rich text editor framework, built for React and backed by an immutable model.
Learn how to use Draft.js in your own project.
Before getting started, please be aware that we recently changed the API of
Entity storage in Draft. The latest version, v0.10.0, supports both the old
and new API. Following that up will be v0.11.0 which will remove the old API.
If you are interested in helping out, or tracking the progress, please follow
issue 839.
npm install --save draft-js react react-dom
or
yarn add draft-js react react-dom
Draft.js depends on React and React DOM which must also be installed.
import React from 'react';
import ReactDOM from 'react-dom';
import {Editor, EditorState} from 'draft-js';
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {editorState: EditorState.createEmpty()};
this.onChange = (editorState) => this.setState({editorState});
this.setEditor = (editor) => {
this.editor = editor;
};
this.focusEditor = () => {
if (this.editor) {
this.editor.focus();
}
};
}
componentDidMount() {
this.focusEditor();
}
render() {
return (
<div style={styles.editor} onClick={this.focusEditor}>
<Editor
ref={this.setEditor}
editorState={this.state.editorState}
onChange={this.onChange}
/>
</div>
);
}
}
const styles = {
editor: {
border: '1px solid gray',
minHeight: '6em'
}
};
ReactDOM.render(
<MyEditor />,
document.getElementById('container')
);
Since the release of React 16.8, you can use Hooks as a way to work with EditorState without using a class.
import React from 'react';
import ReactDOM from 'react-dom';
import {Editor, EditorState} from 'draft-js';
function MyEditor() {
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
const editor = React.useRef(null);
function focusEditor() {
editor.current.focus();
}
React.useEffect(() => {
focusEditor()
}, []);
return (
<div onClick={focusEditor}>
<Editor
ref={editor}
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
/>
</div>
);
}
Note that the editor itself is only as tall as its contents. In order to give users a visual cue, we recommend setting a border and a minimum height via the .DraftEditor-root CSS selector, or using a wrapper div like in the above example.
Because Draft.js supports unicode, you must have the following meta tag in the <head> </head> block of your HTML file:
<meta charset="utf-8" />
Further examples of how Draft.js can be used are provided below.
Visit http://draftjs.org/ to try out a basic rich editor example.
The repository includes a variety of different editor examples to demonstrate some of the features offered by the framework.
To run the examples, first build Draft.js locally. The Draft.js build is tested with Yarn v1 only. If you're using any other package manager and something doesn't work, try using yarn v1:
git clone https://github.com/facebook/draft-js.git
cd draft-js
yarn install
yarn run build
then open the example HTML files in your browser.
Draft.js is used in production on Facebook, including status and comment inputs, Notes, and messenger.com.
IE / Edge | Firefox | Chrome | Safari | iOS Safari | Chrome for Android |
|---|---|---|---|---|---|
| IE11, Edge [1, 2] | last 2 versions | last 2 versions | last 2 versions | not fully supported [3] | not fully supported [3] |
[1] May need a shim or a polyfill for some syntax used in Draft.js (docs).
[2] IME inputs have known issues in these browsers, especially Korean (docs).
[3] There are known issues with mobile browsers, especially on Android (docs).
Check out this curated list of articles and open-sourced projects/utilities: Awesome Draft-JS.
Join our Slack team!
We actively welcome pull requests. Learn how to contribute.
Draft.js is MIT licensed.
Examples provided in this repository and in the documentation are separately licensed.