slate Documentation

A completely customizable framework for building rich text editors. (Currently in beta.)

31,739 stars TypeScript TypeScript 1 file Β· ~13,681 tokens #browser#editor#framework#javascript#react#rich-text#slate#text-editor
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy slate on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name slate --restart always ianstormtaylor/slate:latest
Configured Services & Ports
βš™οΈ test-runner
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
services:
  test-runner:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ../..:/app:ro
      - ../../test-results:/app/test-results:rw
    environment:
      - CI=true
      - PLAYWRIGHT_BASE_URL=http://host.docker.internal:3000
      - NODE_PATH=/usr/local/lib/node_modules
    extra_hosts:
      - "host.docker.internal:host-gateway"
      - "localhost:host-gateway"
    stdin_open: false
    tty: false

File: Readme.md

Slate lets you build rich, intuitive editors like those in Medium, Dropbox Paper or Google Docsβ€”which are becoming table stakes for applications on the webβ€”without your codebase getting mired in complexity.

It can do this because all of its logic is implemented with a series of plugins, so you aren't ever constrained by what is or isn't in "core". You can think of it like a pluggable implementation of contenteditable built on top of React. It was inspired by libraries like Draft.js, Prosemirror and Quill.

πŸ€– Slate is currently in beta. Its core API is useable right now, but you might need to pull request improvements for advanced use cases, or fixes for some bugs. Some of its APIs are not "finalized" and will have breaking changes over time as we discover better solutions. There isn't currently a 1.0 release schedule, we're still getting the architecture right.

πŸ€– Slate is also contributor-driven. It is not backed by any huge company, which means that all contributions are voluntary and done by the people who need them. If you need something improved, added, or fixed, please contribute it yourself or no one will. And if you want to become a more active maintainer, let us know in the Slack channel.

Why?

Why create Slate? Well... (Beware: this section has a few of my opinions!)

Before creating Slate, I tried a lot of the other rich text libraries out thereβ€”Draft.js, Prosemirror, Quill, etc. What I found was that while getting simple examples to work was easy enough, once you started trying to build something like Medium, Dropbox Paper or Google Docs, you ran into deeper issues...

  • The editor's "schema" was hardcoded and hard to customize. Things like bold and italic were supported out of the box, but what about comments, or embeds, or even more domain-specific needs?

  • Transforming the documents programmatically was very convoluted. Writing as a user may have worked, but making programmatic changes, which is critical for building advanced behaviors, was needlessly complex.

  • Serializing to HTML, Markdown, etc. seemed like an afterthought. Simple things like transforming a document to HTML or Markdown involved writing lots of boilerplate code, for what seemed like very common use cases.

  • Re-inventing the view layer seemed inefficient and limiting. Most editors rolled their own views, instead of using existing technologies like React, so you have to learn a whole new system with new "gotchas".

  • Collaborative editing wasn't designed for in advance. Often the editor's internal representation of data made it impossible to use to for a realtime, collaborative editing use case without basically rewriting the editor.

  • The repositories were monolithic, not small and reusable. The code bases for many of the editors often didn't expose the internal tooling that could have been re-used by developers, leading to having to reinvent the wheel.

  • Building complex, nested documents was impossible. Many editors were designed around simplistic "flat" documents, making things like tables, embeds and captions difficult to reason about and sometimes impossible.

Of course not every editor exhibits all of these issues, but if you've tried using another editor you might have run into similar problems. To get around the limitations of their API's and achieve the user experience you're after, you have to resort to very hacky things. And some experiences are just plain impossible to achieve.

If that sounds familiar, you might like Slate.

Which brings me to how Slate solves all of that...

Principles

Slate tries to solve the question of "Why?" with a few principles:

  1. First-class plugins. The most important part of Slate is that plugins are first-class entities. That means you can completely customize the editing experience, to build complex editors like Medium's or Dropbox's, without having to fight against the library's assumptions.

  2. Schema-less core. Slate's core logic assumes very little about the schema of the data you'll be editing, which means that there are no assumptions baked into the library that'll trip you up when you need to go beyond the most basic use cases.

  3. Nested document model. The document model used for Slate is a nested, recursive tree, just like the DOM itself. This means that creating complex components like tables or nested block quotes are possible for advanced use cases. But it's also easy to keep it simple by only using a single level of hierarchy.

  4. Parallel to the DOM. Slate's data model is based on the DOMβ€”the document is a nested tree, it uses selections and ranges, and it exposes all the standard event handlers. This means that advanced behaviors like tables or nested block quotes are possible. Pretty much anything you can do in the DOM, you can do in Slate.

  5. Intuitive commands. Slate documents are edited using "commands", that are designed to be high-level and extremely intuitive to write and read, so that custom functionality is as expressive as possible. This greatly increases your ability to reason about your code.

  6. Collaboration-ready data model. The data model Slate usesβ€”specifically how operations are applied to the documentβ€”has been designed to allow for collaborative editing to be layered on top, so you won't need to rethink everything if you decide to make your editor collaborative.

  7. Clear "core" boundaries. With a plugin-first architecture, and a schema-less core, it becomes a lot clearer where the boundary is between "core" and "custom", which means that the core experience doesn't get bogged down in edge cases.

Demo

Check out the live demo of all of the examples!

Examples

To get a sense for how you might use Slate, check out a few of the examples:

  • Plain text β€” showing the most basic case: a glorified <textarea>.
  • Rich text β€” showing the features you'd expect from a basic editor.
  • Markdown preview β€” showing how to add key handlers for Markdown-like shortcuts.
  • Inlines β€” showing how wrap text in inline nodes with associated data.
  • Images β€” showing how to use void (text-less) nodes to add images.
  • Hovering toolbar β€” showing how a hovering toolbar can be implemented.
  • Tables β€” showing how to nest blocks to render more advanced components.
  • Paste HTML β€” showing how to use an HTML serializer to handle pasted HTML.
  • Mentions β€” showing how to use inline void nodes for simple @-mentions.
  • See all the examples...

If you have an idea for an example that shows a common use case, pull request it!

Documentation

If you're using Slate for the first time, check out the Getting Started walkthroughs and the Concepts to familiarize yourself with Slate's architecture and mental models.

If even that's not enough, you can always read the source itself, which is heavily commented.

There are also translations of the documentation into other languages:

If you're maintaining a translation, feel free to pull request it here!

Packages

Slate's codebase is monorepo managed with Lerna. It consists of a handful of packagesβ€”although you won't always use all of them. They are:

Package Version Size Description
slate Slate's core data model logic.
slate-history A plugin that adds undo/redo history to Slate.
slate-hyperscript A hyperscript tool to write JSX Slate documents!
slate-react React components for rendering Slate editors.

Contributing!

All contributions are super welcome! Check out the Contributing instructions for more info!

Slate is MIT-licensed.


File: docs/general/contributing.md

Contributing

Want to contribute to Slate? That would be awesome!

Reporting Bugs

If you run into any weird behavior while using Slate, feel free to open a new issue in this repository! Please run a search before opening a new issue, to make sure that someone else hasn't already reported or solved the bug you've found.

Any issue you open must include:

  • A JSFiddle that reproduces the bug with a minimal setup.
  • A GIF showing the issue in action. (Using something like RecordIt.)
  • A clear explanation of what the issue is.

Here's a JSFiddle template for Slate to get you started:

Asking Questions

ℹ️ If you're looking for help with the Slate CRM platform, that's an unrelated project to us.

We've also got a Slate Slack team where you can ask questions and get answers from other people using Slate:

Please use the Slack instead of asking questions in issues, since we want to reserve issues for keeping track of bugs and features. We close questions in issues so that maintaining the project isn't overwhelming.

Submitting Pull Requests

All pull requests are super welcomed and greatly appreciated! Issues in need of a solution are marked with a β™₯ help label if you're looking for somewhere to start.

Please include tests and docs with every pull request!

Repository Setup

The Slate repository is a monorepo that is managed with lerna. Unlike more traditional repositories, this means that the repository must be built in order for tests, linting, or other common development activities to function as expected.

To run the build, you need to have the Slate repository cloned to your computer. After that, you need to cd into the directory where you cloned it, and install the dependencies with yarn and build the monorepo:

text
yarn install
yarn build

Running Examples

To run the examples, start by building the monorepo as described in the Repository Setup section.

Then you can start the examples server with:

text
yarn start

Running Tests

To run the tests, start by building the monorepo as described in the Repository Setup section.

Then you can rerun the tests with:

text
yarn test

If you need to debug something, you can add a debugger line to the source, and then run yarn test:inspect.

If you only want to run a specific test or tests, you can run yarn run test:mocha --fgrep="slate-react rendering" flag which will filter the tests being run by grepping for the string in each test. (This is a Mocha flag that gets passed through.)

In addition to tests you should also run the linter:

text
yarn lint

This will catch TypeScript, Prettier, and Eslint errors.

text
yarn fix

This will fix Prettier and Eslint errors.

Running integration tests

To run integrations with Playwright, first run yarn start to run the examples website, then run yarn playwright in a separate session to open the Playwright test suite. Or alternatively, run just yarn test:integration-local.

Running integration tests in Docker

If tests fail on CI but pass locally (often due to OS differences), you can run tests in a Docker container that replicates the same environment as CI.

Prerequisites: The project must be built first (same as running tests locally).

text
yarn test:integration-docker

The script will automatically:

  1. Start the development server (if not already running)
  2. Run tests inside a Docker container
  3. Stop the server when tests complete

You can also pass additional arguments to the test runner. For example, to run a specific test file:

text
yarn test:integration-docker playwright/integration/slate-react/selection.test.ts

Or run a specific browser project:

text
yarn test:integration-docker --project=chromium

You can combine arguments as well:

text
yarn test:integration-docker playwright/integration/examples/check-lists.test.ts --project chromium

Testing Input Methods

Here's a helpful page detailing how to test various input scenarios on Windows, Mac and Linux.

Android tests

When making changes that might affect Android compatibility, you can perform the manual Android tests at /examples/android-tests.

Publishing Releases

Important: When creating releases using Lerna with the instructions below, you will be given choices around how to increase version numbers. You should always use a major, minor or patch release and must never use a prerelease. If a prerelease is used, the root package will not link to the packages in the packages directory creating hard to diagnose issues.

Publishing Normal `@latest` Release

Since we use Lerna to manage the Slate packages this is fairly easy, just run:

text
yarn release:latest

And follow the prompts Lerna gives you.

Note that this will automatically run the prelease script first that will build, test and lint before attempting to publish.

Publishing `@next` Release

If we are unsure as to the stability of a release because there are significant changes and/or particularly complex changes, release with the @next tag.

text
yarn release:next

And follow the prompts Lerna gives you.

Publishing `@experimental` Release

If you need to create an experimental release to see how a published package will behave during an actual publish, release with the @experimental tag. End users should have no expectation that an @experimental release will be usable.

text
yarn release:experimental

Running Prerelease Script

If we want to make sure that Slate code follows the preparations for a release but without actually publishing, run:

text
yarn prerelease

Which will build, test and lint Slate code.


File: docs/general/faq.md

FAQ

A series of common questions people have about Slate:

Why is content pasted as plain text?

One of Slate's core principles is that, unlike most other editors, it does not prescribe a specific "schema" to the content you are editing. This means that Slate's core has no concept of "block quotes" or "bold formatting".

For the most part, this leads to increased flexibility without many downsides, but there are certain cases where you have to do a bit more work. Pasting is one of those cases.

Since Slate knows nothing about your domain, it can't know how to parse pasted HTML content (or other content). So, by default whenever a user pastes content into a Slate editor, it will parse it as plain text. If you want it to be smarter about pasted content, you need to override the insert_data command and deserialize the DataTransfer object's text/html data as you wish.

What browsers and devices does Slate support?

Slate's goal is to support all the modern browsers on both desktop and mobile devices.

Slate is in beta and is community-driven and so its support is not as robust as it could be.

On the desktop, it's currently tested against the latest few versions of Chrome, Edge, Firefox and Safari on desktops. And it does not work in Internet Explorer.

On mobile, iOS devices are supported but not regularly tested. Chrome on Android was until recently unsupported except for in older versions of Slate (0.47) but has recently been added. For clarity, due to the differences in Android's support of the beforeInput event, Android input uses compositions and mutations which is different from other browsers. This means that Android support progresses separately from other browsers and due to it being new, may have more bugs.

If you want to add or improve browser or device support, we'd love for you to submit a pull request! Or in the case of incompatible browsers, build a plugin.

For older browsers, such as IE11, a lot of the now standard native APIs aren't available. Slate's position on this is that it is up to the user to bring polyfills (like https://polyfill-fastly.io/) when needed for things like el.closest, etc. Otherwise we'd have to bundle and maintain lots of polyfills that others may not even need in the first place. For clarity, Slate makes no guarantees that it will work with older browsers, even with polyfills and at present, there are still unresolved issues with IE11.


File: docs/general/resources.md

Resources

A few resources that are helpful for building with Slate.

Libraries

These libraries are helpful when developing with Slate:

  • is-hotkey is a simple way to check whether an onKeyDown handler
    should fire for a given hotkey, handling cross-platform concerns like cmd vs. ctrl keys for you automatically.

Extensions and Plugins

These extensions and plugins add additional features and capabilities to Slate:

  • @liveblocks/yjs A fully-hosted WebSocket infrastructure and
    persisted data store for Yjs documents
  • Plate Rich text editor plugin system for Slate & React
  • slate-angular Angular-based view layer, which is a useful supplement to
    Slate for building a rich text editor using Angular.
  • slate-yjs Collaborative editing utilities for Slate leveraging Yjs
  • slate-collaborative Collaborative editing utilities for Slate
    leveraging Automerge
  • slate-vue3 Which is a useful supplement to Slate for building a rich text editor using Vue3, integrated all functions in an npm package
  • Velt Collaboration SDK adding comments, presence, live cursors and CRDT to Slate

Products

These products use Slate, and can give you an idea of what's possible:

  • Aline: Digital contracting for the modern enterprise
  • Archbee: Build better product docs with your team
  • Campfire: The most versatile writing software [for fiction]
  • Chatterbug: Learn languages from real people
  • CoCalc: Collaborative calculation and data science
  • Coda: Coda brings all your words, data, and teamwork into one powerful doc
  • Decap CMS: Open source content management for your Git workflow
  • Dropdeck: Magically create beautiful slides β€” faster than ever
  • Easy Email Pro: A powerful, highly customizable email editor
  • Eraser: The Whiteboard for Engineering Teams
  • GitBook: Plan, centralize and share knowledge
  • Discord: Text & voice channels to collaborate & share
  • Grafana: Operational dashboards for your data
  • Guilded: Better chat, happier communities
  • Guru: Organize company information. Access it anywhere. [wiki]
  • Hygraph: The federated content platform
  • Hot Page: A graphical website builder for writing HTML and CSS from scratch
  • Kitemaker: Product development tool built to manage the work, not the workers
  • Living Spec: A modern way for product teams to work
  • Notestamp: A note-taking web app that synchronizes your notes to audio, video and pdfs.
  • Notes Together: Get mundane details out of your head
  • Prezly: Turn your audience into fans
  • Quill Forms: Open Source Typeform Alternative WordPress Plugin
  • Sana: Reimagining learning with AI [LMS]
  • Sanity.io: Platform for structured content [data driven content]
  • Slite: Where remote teams make decisions and share knowledge
  • Static CMS: Git-based CMS for Static Site Generators
  • Taskade: Get work done in one unified workspace
  • TRPG Engine
  • Thoughts

Editors

These pre-packaged editors are built on top of Slate, and can be helpful to see how you might structure your code:

(Or, if you have their exact use case, can be a drop-in editor for you.)


File: docs/concepts/01-interfaces.md

Interfaces

Slate works with pure JSON objects. All it requires is that those JSON objects conform to certain interfaces. For example, a text node in Slate must obey the Text interface:

typescript
interface Text {
  text: string
}

Which means it must have a text property with a string of content.

But any other custom properties are also allowed, and completely up to you. This lets you tailor your data to your specific domain and use case, adding whatever formatting logic you'd like, without Slate getting in the way.

This interface-based approach separates Slate from most other rich text editors which require you to work with their hand-rolled "model" classes and makes it much easier to reason about. It also means that it avoids startup time penalties related to "initializing" the data model.

Custom Properties

To take another example, the Element node interface in Slate is:

typescript
interface Element {
  children: Node[]
}

This is a very permissive interface. All it requires is that the children property gets defined containing the element's child nodes.

But you can extend elements (or any other interface) with your custom properties that are specific to your domain. For example, you might have "paragraph" and "link" elements:

javascript
const paragraph = {
  type: 'paragraph',
  children: [...],
}

const link = {
  type: 'link',
  url: 'https://example.com',
  children: [...]
}

The type and url properties are your custom API. Slate sees that they exist, but doesn't use them. However, when Slate renders a link element, you'll receive an object with the custom properties attached so that you can render it as:

jsx
<a href={element.url}>{element.children}</a>

When getting started with Slate, it's important to understand all of the interfaces it defines. There are a handful of interfaces that are discussed in each of the guides.

Helper Functions

In addition to the typing information, each interface in Slate also exposes a series of helper functions that make them easier to work with.

For example, when working with nodes:

javascript
import { Node } from 'slate'

// Get the string content of an element node.
const string = Node.string(element)

// Get the node at a specific path inside a root node.
const descendant = Node.get(value, path)

Or, when working with ranges:

javascript
import { Range } from 'slate'

// Get the start and end points of a range in order.
const [start, end] = Range.edges(range)

// Check if a range is collapsed to a single point.
if (Range.isCollapsed(range)) {
  // ...
}

There are many helper functions available for all common use cases when working with different interfaces. When getting started it helps to read through all of them so you can often simplify complex logic into just a handful of lines of code.

Custom Helpers

In addition to the built-in helper functions, you might want to define your custom helper functions and expose them on your custom namespaces.

For example, if your editor supports images, you might want a helper that determines if an element is an image element:

javascript
const isImageElement = element => {
  return element.type === 'image' && typeof element.url === 'string'
}

You can define these as one-off functions easily. But you might also bundle them up into namespaces, just like the core interfaces do, and use them instead.

For example:

javascript
import { Element } from 'slate'

// You can use `MyElement` everywhere to have access to your extensions.
export const MyElement = {
  ...Element,
  isImageElement,
  isParagraphElement,
  isQuoteElement,
}

This makes it easy to reuse domain-specific logic alongside the built-in Slate helpers.


File: docs/concepts/02-nodes.md

Nodes

The most important types are the Node objects:

  • A root-level Editor node that contains the entire document's content.
  • Container Element nodes that have semantic meaning in your domain.
  • And leaf-level Text nodes which contain the document's text.

These three interfaces are combined to form a treeβ€”just like the DOM. For example, here's a simple plaintext value:

javascript
const editor = {
  children: [
    {
      type: 'paragraph',
      children: [
        {
          text: 'A line of text!',
        },
      ],
    },
  ],
  // ...the editor has other properties too.
}

Mirroring the DOM as much as possible is one of Slate's principles. People use the DOM to represent documents with rich text-like structures all the time. Mirroring the DOM helps make the library familiar for new users, and it lets us reuse battle-tested patterns without having to reinvent them ourselves.

πŸ€– The following content on Mozilla's Developer Network may help you learn more about the corresponding DOM concepts:

A Slate document is a nested and recursive structure. In a document, elements can have children nodesβ€”all of which may have children nodes without limit. The nested and recursive structure enables you to model simple behaviors such as user mentions and hashtags or complex behaviors such as tables and figures with captions.

`Editor`

The top-level node in a Slate document is the Editor itself. It encapsulates all of the rich text "content" of the document. Its interface is:

typescript
interface Editor {
  children: Node[]
  ...
}

We'll cover its functionality later, but the important part as far as nodes are concerned is its children property which contains a tree of Node objects.

`Element`

Elements make up the middle layers of a rich text document. They are the nodes that are custom to your domain. Their interface is:

typescript
interface Element {
  children: Node[]
}

You can define custom elements for any type of content you want. For example, you might have paragraphs and quotes in your data model which are differentiated by a type property:

javascript
const paragraph = {
  type: 'paragraph',
  children: [...],
}

const quote = {
  type: 'quote',
  children: [...],
}

It's important to note that you can use any custom properties you want. The type property in that example isn't something Slate knows or cares about. If you were defining your own "link" nodes, you might have a url property:

javascript
const link = {
  type: 'link',
  url: 'https://example.com',
  children: [...],
}

Or maybe you want to give all of your nodes an ID property:

javascript
const paragraph = {
  id: 1,
  type: 'paragraph',
  children: [...],
}

All that matters is that elements always have a children property.

Blocks vs. Inlines

Depending on your use case, you might want to define another behavior for Element nodes which determines their editing "flow".

All elements default to being "block" elements. They each appear separated by vertical space, and they never run into each other.

But in certain cases, like for links, you might want to make them "inline" flowing elements instead. That way they live at the same level as text nodes, and flow.

πŸ€– This is a concept borrowed from the DOM's behavior, see Block Elements and Inline Elements.

You can define which nodes are treated as inline nodes by overriding the editor.isInline function. (By default it always returns false.) Note that inline nodes cannot be the first or last child of a parent block, nor can they be next to another inline node in the children array. Slate will automatically space these with { text: '' } children by default with normalizeNode.

Elements can either contain block elements or inline elements intermingled with text nodes as children. But elements cannot contain some children that are blocks and some that are inlined.

Voids

Similar to blocks and inlines, there is another element-specific behavior you can define depending on your use case: their "void"-ness.

Elements default to being non-void, meaning that their children are fully editable as text. But in some cases, like for images, you want to ensure that Slate doesn't treat their content as editable text, but instead as a black box.

πŸ€– This is a concept borrowed from the HTML spec, see Void Elements.

You can define which elements are treated as void by overriding the editor.isVoid function. (By default it always returns false.) See Rendering Void Elements for implementation details.

`Text`

Text nodes are the lowest-level nodes in the tree, containing the text content of the document, along with any formatting. Their interface is:

typescript
interface Text {
  text: string
}

For example, a string of bold text:

javascript
const text = {
  text: 'A string of bold text',
  bold: true,
}

Text nodes too can contain any custom properties you want, and that's how you implement custom formatting like bold, italic, code, etc.
These custom properties are sometimes called marks.


File: docs/concepts/03-locations.md

Locations

Locations are how you refer to specific places in the document when inserting, deleting, or doing anything else with a Slate editor. There are a few different kinds of location interfaces, each for different use cases.

`Path`

Paths are the lowest-level way to refer to a location. Each path is a simple array of numbers that refers to a node in the document tree by its indexes in each of its ancestor nodes down the tree:

typescript
type Path = number[]

For example, in this document:

javascript
const editor = {
  children: [
    {
      type: 'paragraph',
      children: [
        {
          text: 'A line of text!',
        },
      ],
    },
  ],
}

The leaf text node would have a path of: [0, 0].

The Editor itself has a path of []. For example, to select the whole contents of the editor, call
Transforms.select(editor, [])

`Point`

Points are slightly more specific than paths, and contain an offset into a specific text node. Their interface is:

typescript
interface Point {
  path: Path
  offset: number
}

For example, with that same document, if you wanted to refer to the very first place you could put your cursor it would be:

javascript
const start = {
  path: [0, 0],
  offset: 0,
}

Or, if you wanted to refer to the end of the sentence:

javascript
const end = {
  path: [0, 0],
  offset: 15,
}

It can be helpful to think of points as being "cursors" (or "carets") of a selection.

πŸ€– Points always refer to text nodes! Since they are the only ones with strings that can have cursors.

`Range`

Ranges are a way to refer not just to a single point in the document, but to a wider span of content between two points. (An example of a range is when you make a selection.) Their interface is:

typescript
interface Range {
  anchor: Point
  focus: Point
}

πŸ€– The terms "anchor" and "focus" are borrowed from the DOM, see Anchor and Focus.

An anchor and focus are established by a user interaction. The anchor point isn't always before the focus point in the document. Just like in the DOM, the ordering of an anchor and selection point depend on whether the range is backwards or forwards.

Here's how Mozilla Developer Network explains it:

A user may make a selection from left to right (in document order) or right to left (reverse of document order). The anchor is where the user began the selection and the focus is where the user ends the selection. If you make a selection with a desktop mouse, the anchor is placed where you pressed the mouse button and the focus is placed where you released the mouse button. Anchor and focus should not be confused with the start and end positions of a selection, since anchor can be placed before the focus or vice versa, depending on the direction you made your selection. β€” Selection, MDN

One important distinction is that the anchor and focus points of ranges always reference the leaf-level text nodes in a document and never reference elements. This behavior is different than the DOM, but it simplifies working with ranges as there are fewer edge cases for you to handle.

πŸ€– For more info, check out the Range API reference.

Selection

Ranges are used in many places in Slate's API when you need to refer to a span of content between two points. One of the most common, though, is the user's current "selection".

The selection is a special range that is a property of the top-level Editor. For example, say someone has the whole sentence currently selected:

javascript
const editor = {
  selection: {
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [0, 0], offset: 15 },
  },
  children: [
    {
      type: 'paragraph',
      children: [
        {
          text: 'A line of text!',
        },
      ],
    },
  ],
}

πŸ€– The selection concept is also borrowed from the DOM, see Selection, MDN, although in a greatly-simplified form because Slate doesn't allow for multiple ranges inside a single selection, which makes things a lot easier to work with.

There isn't a special Selection interface. It's just an object that happens to respect the more general-purpose Range interface instead.

For example, to find the lowest block that contains all of the current selection:

javascript
function getCommonBlock(editor) {
  const range = Editor.unhangRange(editor, editor.selection, { voids: true })

  let [common, path] = SlateNode.common(
    editor,
    range.anchor.path,
    range.focus.path
  )

  if (Editor.isBlock(editor, common) || Editor.isEditor(common)) {
    return [common, path]
  } else {
    return Editor.above(editor, {
      at: path,
      match: n => Editor.isBlock(editor, n) || Editor.isEditor(n),
    })
  }
}

File: docs/concepts/04-transforms.md

Transforms

Slate's data structure is immutable, so you can't modify or delete nodes directly. Instead, Slate comes with a collection of "transform" functions that let you change your editor's value.

Slate's transform functions are designed to be very flexible, to make it possible to represent all kinds of changes you might need to make to your editor. However, that flexibility can be hard to understand at first.

Typically, you'll apply a single operation to zero or more Nodes. For example, here's how you flatten the syntax tree,
by applying unwrapNodes to every parent of block Elements:

javascript
Transforms.unwrapNodes(editor, {
  at: [], // Path of Editor
  match: node =>
    !Editor.isEditor(node) &&
    node.children?.every(child => Editor.isBlock(editor, child)),
  mode: 'all', // also the Editor's children
})

Non-standard operations (or debugging/tracing which Nodes will be affected by a set of NodeOptions) may require using
Editor.nodes to create a JavaScript Iterator of NodeEntries and a for..of loop to act.
For example, to replace all image elements with their alt text:

javascript
const imageElements = Editor.nodes(editor, {
  at: [], // Path of Editor
  match: (node, path) => 'image' === node.type,
  // mode defaults to "all", so this also searches the Editor's children
})
for (const nodeEntry of imageElements) {
  const altText =
    nodeEntry[0].alt ||
    nodeEntry[0].title ||
    /\/([^/]+)$/.exec(nodeEntry[0].url)?.[1] ||
    '☹︎'
  Transforms.select(editor, nodeEntry[1])
  Editor.insertFragment(editor, [{ text: altText }])
}

πŸ€– Check out the Transforms reference for a full list of Slate's transforms.

Selection Transforms

Selection-related transforms are some of the simpler ones. For example, here's how you set the selection to a new range:

javascript
Transforms.select(editor, {
  anchor: { path: [0, 0], offset: 0 },
  focus: { path: [1, 0], offset: 2 },
})

But they can be more complex too.

For example, it's common to need to move a cursor forwards or backwards by varying distancesβ€”by character, by word, by line. Here's how you'd move the cursor backwards by three words:

javascript
Transforms.move(editor, {
  distance: 3,
  unit: 'word',
  reverse: true,
})

πŸ€– For more info, check out the Selection Transforms API Reference

Text Transforms

Text transforms act on the text content of the editor. For example, here's how you'd insert a string of text as a specific point:

javascript
Transforms.insertText(editor, 'some words', {
  at: { path: [0, 0], offset: 3 },
})

Or you could delete all of the content in an entire range from the editor:

javascript
Transforms.delete(editor, {
  at: {
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [1, 0], offset: 2 },
  },
})

πŸ€– For more info, check out the Text Transforms API Reference

Node Transforms

Node transforms act on the individual element and text nodes that make up the editor's value. For example you could insert a new text node at a specific path:

javascript
Transforms.insertNodes(
  editor,
  {
    text: 'A new string of text.',
  },
  {
    at: [0, 1],
  }
)

Or you could move nodes from one path to another:

javascript
Transforms.moveNodes(editor, {
  at: [0, 0],
  to: [0, 1],
})

πŸ€– For more info, check out the Node Transforms API Reference

The `at` Option

Many transforms act on a specific location in the document. By default, they will use the user's current selection. But this can be overridden with the at option.

For example when inserting text, this would insert the string at the user's current cursor:

javascript
Transforms.insertText(editor, 'some words')

Whereas this would insert it at a specific point:

javascript
Transforms.insertText(editor, 'some words', {
  at: { path: [0, 0], offset: 3 },
})

The at option is very versatile, and can be used to implement more complex transforms very easily. Since it is a Location it can always be either a Path, Point, or Range. And each of those types of locations will result in slightly different transformations.

For example, in the case of inserting text, if you specify a Range location, the range will first be deleted, collapsing to a single point where your text is then inserted.

So to replace a range of text with a new string you can do:

javascript
Transforms.insertText(editor, 'some words', {
  at: {
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [0, 0], offset: 3 },
  },
})

Or, if you specify a Path location, it will expand to a range that covers the entire node at that path. Then, using the range-based behavior it will delete all of the content of the node, and replace it with your text.

So to replace the text of an entire node with a new string you can do:

javascript
Transforms.insertText(editor, 'some words', {
  at: [0, 0],
})

These location-based behaviors work for all the transforms that take an at option. It can be hard to wrap your head around at first, but it makes the API very powerful and capable of expressing many subtly different transforms.

The `match` Option

Many of the node-based transforms take a match function option, which restricts the transform to only apply to nodes for which the function returns true. When combined with at, match can also be very powerful.

For example, consider a basic transform that moves a node from one path to another:

javascript
Transforms.moveNodes(editor, {
  at: [2],
  to: [5],
})

Although it looks like it simply takes a path and moves it to another place. Under the hood two things are happening…

First, the at option is expanded to be a range representing all of the content inside the node at [2]. Which might look something like:

javascript
at: {
  anchor: { path: [2, 0], offset: 0 },
  focus: { path: [2, 2], offset: 19 }
}

Second, the match option is defaulted to a function that only matches the specific path, in this case [2]:

javascript
match: (node, path) => Path.equals(path, [2])

Then Slate iterates over the range and moves any nodes that pass the matcher function to the new location. In this case, since match is defaulted to only match the exact [2] path, that node is moved.

But what if you wanted to move the children of the node at [2] instead?

You might consider looping over the node's children and moving them one at a time, but this gets very complex to manage because as you move the nodes the paths you're referring to become outdated.

Instead, you can take advantage of the at and match options to match all of the children:

javascript
Transforms.moveNodes(editor, {
  // This will again be expanded to a range of the entire node at `[2]`.
  at: [2],
  // Matches nodes with a longer path, which are the children.
  match: (node, path) => path.length === 2,
  to: [5],
})

Here we're using the same at path (which is expanded to a range), but instead of letting it match just that path by default, we're supplying our own match function which happens to match only the children of the node.

Using match can make representing complex logic a lot simpler.

For example, consider wanting to add a bold mark to any text nodes that aren't already italic:

javascript
Transforms.setNodes(
  editor,
  { bold: true },
  {
    // This path references the editor, and is expanded to a range that
    // will encompass all the content of the editor.
    at: [],
    // This only matches text nodes that are not already italic.
    match: (node, path) => Text.isText(node) && node.italic !== true,
  }
)

When performing transforms, if you're ever looping over nodes and transforming them one at a time, consider seeing if match can solve your use case, and offload the complexity of managing loops to Slate instead.
The match function can examine the children of a node, in node.children, or use Node.parent to examine its parent.

Transforms and Normalization

Sequences of Transforms may need to be wrapped in Editor.withoutNormalizing if the node tree should not be normalized between Transforms.
See Normalization - Implications for Other Code;


File: docs/concepts/05-operations.md

Operations

Operations are the granular, low-level actions that occur while invoking transforms. A single transform could result in many low-level operations being applied to the editor.

Slate's core defines all of the possible operations that can occur on a richtext document. For example:

javascript
editor.apply({
  type: 'insert_text',
  path: [0, 0],
  offset: 15,
  text: 'A new string of text to be inserted.',
})

editor.apply({
  type: 'remove_node',
  path: [0, 0],
  node: {
    text: 'A line of text!',
  },
})

editor.apply({
  type: 'set_selection',
  properties: {
    anchor: { path: [0, 0], offset: 0 },
  },
  newProperties: {
    anchor: { path: [0, 0], offset: 15 },
  },
})

Under the covers Slate converts complex transforms into the low-level operations and applies them to the editor automatically. So you rarely have to think about operations unless you're implementing collaborative editing.

πŸ€– Slate's editing behaviors being defined as operations is what makes things like collaborative editing possible, because each change is easily define-able, apply-able, compose-able and even undo-able!


File: docs/concepts/06-commands.md

Commands

While editing richtext content, your users will be doing things like inserting text, deleting text, splitting paragraphs, adding formatting, etc. Under the cover these edits are expressed using transforms and operations. But at a high level we talk about them as "commands".

Commands are the high-level actions that represent a specific intent of the user. They are represented as helper functions on the Editor interface. A handful of helpers are included in core for common richtext behaviors, but you are encouraged to write your own that model your specific domain.

For example, here are some of the built-in commands:

javascript
Editor.insertText(editor, 'A new string of text to be inserted.')

Editor.deleteBackward(editor, { unit: 'word' })

Editor.insertBreak(editor)

But you can (and will!) also define your own custom commands that model your domain. For example, you might want to define a formatQuote command, or an insertImage command, or a toggleBold command depending on what types of content you allow.

Commands always describe an action to be taken as if the user was performing the action. For that reason, they never need to define a location to perform the command, because they always act on the user's current selection.

πŸ€– The concept of commands is loosely based on the DOM's built-in execCommand APIs. However Slate defines its own simpler (and extendable!) version of the API, because the DOM's version is too opinionated and inconsistent.

Under the covers, Slate takes care of converting each command into a set of low-level "operations" that are applied to produce a new value. This is what makes collaborative editing implementations possible. But you don't have to worry about that, because it happens automatically.

Custom Commands

When defining custom commands, you can create your own namespace:

javascript
const MyEditor = {
  ...Editor,

  insertParagraph(editor) {
    // ...
  },
}

When writing your own commands, you'll often make use of the Transforms helpers that ship with Slate.

Transforms

Transforms are a specific set of helpers that allow you to perform a wide variety of specific changes to the document, for example:

javascript
// Set a "bold" format on all of the text nodes in a range.
// Normally you would apply a style like bold using the Editor.addMark() command.
// The addMark() command performs a similar setNodes transform, but it uses a more
// complicated match function in order to apply marks within markableVoid elements.
Transforms.setNodes(
  editor,
  { bold: true },
  {
    at: range,
    match: node => Text.isText(node),
    split: true,
  }
)

// Wrap the lowest block at a point in the document in a quote block.
Transforms.wrapNodes(
  editor,
  { type: 'quote', children: [] },
  {
    at: point,
    match: node => Editor.isBlock(editor, node),
    mode: 'lowest',
  }
)

// Insert new text to replace the text in a node at a specific path.
Transforms.insertText(editor, 'A new string of text.', { at: path })

// ...there are many more transforms!

The transform helpers are designed to be composed together. So you might use a handful of them for each command.