Slate: Customizable Rich Text Editor Framework
Slate is a unopinionated and completely customizable framework for building rich text editors, providing a foundational set of primitives rather than a monolithic solution. It allows developers to create bespoke editing experiences by composing plugins and extending its core data model. Currently at version `0.124.1` (core `slate` package), it maintains an active development pace with frequent patch and minor releases across its monorepo packages (e.g., `slate-react`, `slate-history`, `slate-dom`). Key differentiators include its pluggable architecture, a robust data model representing content as a nested tree, and deep integration with React for rendering. This approach enables it to be adapted for a wide range of applications, from simple note-taking to complex document authoring systems, by avoiding prescriptive UI/UX decisions.
Common errors
-
Type 'MyCustomEditor' is not assignable to type 'ReactEditor'. Type 'MyCustomEditor' is missing the following properties from type 'ReactEditor': markIs) and related React DOM properties.
cause Incorrectly extending Slate's built-in TypeScript types (Editor, Element, Text) or failing to declare the custom types module.fixEnsure you have a `declare module 'slate' { interface CustomTypes { Editor: MyCustomEditor; Element: MyCustomElement; Text: MyCustomText; } }` block in a `.d.ts` file or at the top of your relevant TypeScript file, and that your custom types correctly extend the base Slate types (`ReactEditor`, `BaseElement`, `BaseText`). -
Error: Editor instance not found in context. This is likely because you've used `useEditor` outside of a `<Slate>` context.
cause Attempting to use `Editable` or any Slate-related hooks (`useEditor`, `useSlate`, etc.) outside of a `<Slate>` component context.fixWrap your `Editable` component and any custom components utilizing Slate hooks within the `<Slate editor={editor} value={value} onChange={setValue}>` provider component. -
Cannot read properties of undefined (reading 'children') when rendering a custom element.
cause Your `renderElement` or `renderLeaf` function is not correctly handling different types of nodes or is expecting properties that aren't present on the given node.fixAdd a `default` case to your `switch` statement in `renderElement` and `renderLeaf` to handle unexpected node types gracefully. Ensure all custom element types you define in `CustomTypes` are explicitly handled in `renderElement`.
Warnings
- breaking Slate's `0.x` versioning scheme means that minor versions (`0.x.y` to `0.z.w` where `x != z`) can introduce significant breaking changes to the API and internal data structures. Users should always review the changelog when updating minor versions.
- gotcha The `slate@0.123.0` release introduced new, more efficient type discriminators like `Location.isPath`, `Location.isPoint`, `Location.isRange`, and `Location.isSpan`. While the older `Path.isPath`, `Point.isPoint`, etc., still exist, the new `Location` prefixed versions are recommended for better performance and consistency.
- gotcha Ensuring the `Slate` component properly handles editor updates in `slate-react` requires careful management of the editor instance. If the `editor` object itself is re-created or mutated in a way that React's `useEffect` hooks don't track, the editor might not reflect changes correctly.
Install
-
npm install slate -
yarn add slate -
pnpm add slate
Imports
- createEditor
const createEditor = require('slate').createEditor;import { createEditor } from 'slate'; - Slate, Editable, withReact
const { Slate, Editable, withReact } = require('slate-react');import { Slate, Editable, withReact } from 'slate-react'; - Descendant, Editor, Element, Text
import { Descendant, Editor, Element, Text } from 'slate'; - withHistory
import { withHistory } from 'slate-history';
Quickstart
import React, { useMemo, useState } from 'react';
import { createEditor, Descendant } from 'slate';
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
import { withHistory } from 'slate-history';
// Define our custom editor and element types for TypeScript to enable type safety
type CustomEditor = ReactEditor;
type ParagraphElement = { type: 'paragraph'; children: CustomText[] };
type CustomText = { text: string; bold?: boolean; italic?: boolean };
declare module 'slate' {
interface CustomTypes {
Editor: CustomEditor;
Element: ParagraphElement;
Text: CustomText;
}
}
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [{ text: 'Hello, Slate!' }],
},
{
type: 'paragraph',
children: [{ text: 'This is some ' }, { text: 'bold', bold: true }, { text: ' text.' }],
},
];
const MySimpleEditor = () => {
// Create a Slate editor object that won't change across renders.
// It's enhanced with React capabilities and history (undo/redo).
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
const [value, setValue] = useState<Descendant[]>(initialValue);
return (
<Slate editor={editor} value={value} onChange={setValue}>
<Editable
placeholder="Start typing..."
// Define a rendering function for elements
renderElement={props => <p {...props.attributes}>{props.children}</p>}
// Define a rendering function for leaf nodes (text with formatting)
renderLeaf={props => {
let children = props.children;
if (props.leaf.bold) children = <strong>{children}</strong>;
if (props.leaf.italic) children = <em>{children}</em>;
return <span {...props.attributes}>{children}</span>;
}}
style={{ border: '1px solid #ccc', padding: '10px', minHeight: '100px' }}
/>
</Slate>
);
};
export default MySimpleEditor;