TypeScript

Slate supports typing of one Slate document model (ie. one set of custom Editor, Element and Text types). If you need to support more than one document model, see the section Multiple Document Models.

Warning: You must define CustomTypes, annotate useState, and annotate the editor's initial state when using TypeScript or Slate will display typing errors.

Migrating from 0.47.x

When migrating from 0.47.x, read the guide below first. Also keep in mind these common migration issues:

  • When referring to node.type, you may see the error Property 'type' does not exist on type 'Node'. To fix this, you need to add code like Element.isElement(node) && node.type === 'paragraph'. This is necessary because a Node can be an Element or Text and Text does not have a type property.

  • Be careful when you define the CustomType for Editor. Make sure to define the CustomType for Editor as BaseEditor & .... It should not be Editor & ...

Defining Editor, Element and Text Types

To define a custom Element or Text type, extend the CustomTypes interface in the slate module like this.

// This example is for an Editor with `ReactEditor` and `HistoryEditor`
import { BaseEditor } from 'slate'
import { ReactEditor } from 'slate-react'
import { HistoryEditor } from 'slate-history'
type CustomElement = { type: 'paragraph'; children: CustomText[] }
type CustomText = { text: string; bold?: true }
declare module 'slate' {
interface CustomTypes {
Editor: BaseEditor & ReactEditor & HistoryEditor
Element: CustomElement
Text: CustomText
}
}

Annotations in the Editor

Annotate useState w/ <Descendant[]> and the editor's initial value w/ your custom Element type.

import React, { useMemo, useState } from 'react'
import { createEditor, Descendant } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const App = () => {
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState<Descendant[]>(initialValue)
return (
<Slate editor={editor} value={value} onChange={setValue}>
<Editable />
</Slate>
)
}

Best Practices for Element and Text Types

While you can define types directly in the CustomTypes interface, best practice is to define and export each type separately so that you can reference individual types like a ParagraphElement.

Using best practices, the custom types might look something like:

// This example is for an Editor with `ReactEditor` and `HistoryEditor`
import { BaseEditor } from 'slate'
import { ReactEditor } from 'slate-react'
import { HistoryEditor } from 'slate-history'
export type CustomEditor = BaseEditor & ReactEditor & HistoryEditor
export type ParagraphElement = {
type: 'paragraph'
children: CustomText[]
}
export type HeadingElement = {
type: 'heading'
level: number
children: CustomText[]
}
export type CustomElement = ParagraphElement | HeadingElement
export type FormattedText = { text: string; bold?: true }
export type CustomText = FormattedText
declare module 'slate' {
interface CustomTypes {
Editor: CustomEditor
Element: CustomElement
Text: CustomText
}
}

In this example, CustomText is equal to FormattedText but in a real editor, there can be more types of text like text in a code block which may not allow formatting for example.

Why Is The Type Definition Unusual

Because it gets asked often, this section explains why Slate's type definition is atypical.

Slate needs to support a feature called type discrimination which is available when using union types (e.g. ParagraphElement | HeadingElement). This allows a user to narrow a type. If presented with code like if (node.type === 'paragraph') { ... } the inside of the block, will narrow the type of node to ParagraphElement.

Slate also needs a way to allow developers to get their custom types into Slate. This is done through declaration merging which is a feature of an interface.

Slate combines a union type and an interface in order to use both features.

For more information see Proposal: Add Custom TypeScript Types to Slate

Multiple Document Models

At the moment, Slate supports types for a single document model at a time. For example, it cannot support two different Rich Text Editor with different document schemas.

Slate's TypeScript support was designed this way because typing for one document schema was better than none. The goal is to eventually support typing for multiple editor definitions and there is currently an in progress PR built by the creator of Slate.

One workaround for supporting multiple document models is to create each editor in a separate package and then import them. This hasn't been tested but should work.

Extending Other Types

Currently there is also support for extending other types but these haven't been tested as thoroughly as the ones documented above:

  • Selection

  • Range

  • Point

Feel free to extend these types but extending these types should be considered experimental. Please report bugs on GitHub issues.

TypeScript Examples

For some examples of how to use types, see packages/slate-react/src/custom-types.ts in the slate repository.