Building an awesome editor for your React-based web application is by no means easy. But with SlateJS things get much easier. Even with the help of Slate, building a full-featured editor is way more work than we can cover in one blog post, so this post will give you the big picture and subsequent posts will dive into the dirty details.
We're building Kitemaker, a new, fast, and highly collaborative alternative to issue trackers like Jira, Trello, and Clubhouse. We're big believers in remote work, and in particular working asynchronously so that team members get large, uninterrupted blocks of time to get work done. A key to supporting this type of work is having a really great editor so that teams are inspired to collaborate together on issues and ensure alignment. And we think we've got the best editor around:
Markdown shortcuts, code blocks with syntax highlighting, images, embedding designs from Figma, math expressions with LaTex, diagrams with MermaidJS, and, of course, emojis ♥️. All done entirely with Slate.
So why did we choose to go with Slate in the first place? It's definitely not the only editor framework out there. For us, the things that pushed us towards Slate were:
One of the best parts of Slate is how it holds very few opinions about how documents are structured. It has only a few concepts:
Astute readers will notice that this is structured very similar to the DOM, and text, block and inlines in Slate behave very much like their counterparts in the DOM.
Here's an annotated screenshot of a Slate editor to explain these concepts visually:
Slate uses a very simple JSON format for representing documents, and the document above would look like this in Slate's representation:
As we said before, Slate is really un-opinionated about how documents are structured. In the JSON blob above, the only thing Slate cares about is that it gets an array of block elements with a children property and that those children are either other block elements, or a mixture of text nodes and inline elements. That's it. Slate doesn't care about the type, url or bold properties and it doesn't care about how these various nodes will be rendered. This makes it really flexible and powerful to work with.
Enough background. Let's look at some code! Let's see what a simple editor component looks like using Slate:
And that's it. That's an entire working component with Slate!
So while that previous example was trivial to write, we still only have an editor that works about as well as <TextArea>. Not very exciting.
Luckily Slate provides mechanisms for making things a whole lot more interesting:
Let's take a quick look at each of these.
Plugins are a deceptively simple, powerful concept in Slate. Plugins generally look something like this:
By convention, their names start with with. They take a Slate editor, override whatever functions they need to override and return the modified editor back. Very often, they handle a few cases in some of these functions and fall back to the default behavior for the rest. Spoiler alert: about 80% of adding functionality to a Slate editor is doing string matching in these plugin functions and then manipulate the document using Slate's rich API.
There are more functions you can override than the ones shown above, but these are the most common by far. You can read all about plugins in Slate's documentation.
One of the most powerful parts of Slate plugins is that they can be composed together. Each plugin can look for the things it cares about and pass everything else along unmodified. Then you can compose multiple plugins together and get an even more powerful editor:
Like many React input components, the <Editable> component has a bunch of events to which you can listen. We don't have time to go through them all here, but we'll mention the one we use most often: onKeyDown()
By handling this event, we can do all sorts of powerful things in our editor, like adding hotkeys for example:
We use key down events everywhere in Kitemaker:
Slate has no opinions on how your blocks and inlines should look on the screen. By default, it just shoves all blocks into plain <div> elements and all inlines into plain <span> elements, but that's pretty dull.
To override Slate's default behavior, all we need to do is pass a function into the <Editable> component's renderElement property:
All this code is doing is looking for the type property on a node and picking a different rendering path based on that. Remember, as we said before, Slate doesn't care about these properties, only we do. So while the convention is to use type to denote the type of a node, nothing is forcing you to do so. You can also add all sorts of other properties to your components that help with the rendering (like the url property we saw on links above).
The things returned from renderElement just need to be React components. What they look like and their complexity is entirely up to you. Here we're returning a simple <pre> element to denote a code block, but nothing is stopping us from returning a full blown <Code> component that supports syntax highlighting (like we do in Kitemaker).
There's only one important thing to remember when implementing your own rendering - always spread the attributes parameter as properties on the topmost component you're returning. If you don't, Slate won't be able to do its own internal bookkeeping and things will go very badly for you.
This has been a super quick introduction to custom rendering, so don't worry if you don't fully grasp it yet.
You've now seen some of the basics of Slate, so you're ready to start experimenting. We thought we'd warn you a little about some of the pitfalls and tricky parts of working with Slate so you'll see them coming and not get discouraged:
We'll cover some of these advanced topics in our subsequent posts.
While we've been very happy with Slate so far, there are a few warnings that any team embarking on building their own editor should be aware of:
We hope this served as a nice high level introduction to Slate for you and gave you some of the information you need about whether or not to give Slate a try. There is way way way too much material to cover in a single post, but there will be a number of posts that follow that dig into some of the more complex topics.
Kevin Simons is the CTO of Kitemaker, the super-fast issue tracker that gives distributed teams superpowers
This is a post about the pain that comes from trying to scale CSS in a large project.
A particular form of procrastination I've found in others and me — failure to just get started.