Once you've picked a rich text library for your application, switching to a new one isn't easy and carries many risks. We recently changed the rich text library we use at Ashby, and, with research and planning, we were able to do it with minimal issues in production. We wanted to share that research and process with the hope that it helps you make a similar switch.
This post is part one of a two-part series about how we switched from Slate to tiptap. Today, we'll explain the process we used to decide on tiptap, and our follow-up post will describe how we made the switch.
Time to Switch
Like many libraries back then, Slate was in beta but quickly improving and expanding. We took a chance that Slate would support more browsers and its API would remain stable (despite the maintainers strongly suggesting that both might not happen!). Unfortunately, our gamble didn't pay off, and the maintainers introduced significant breaking changes to the API and document model in
0.50.x. These changes were the right direction for the future of Slate but would require a lot of work for us to migrate to —work we couldn't do as a small engineering team (even with the great support the community was providing).
We stayed on
0.47.x as long as we could, but our users were growing, and we needed to upgrade Slate to fix issues and support more browsers. There were more rich text editor libraries to choose from since 2019, so we decided to evaluate switching to a new library in addition to upgrading Slate.
Find All Possible Candidates
We created a comprehensive list of rich text editor libraries to evaluate. While Google is useful, ranking in Google Search is a popularity contest, not a quality test. To be exhaustive, we started with the initial list of libraries we found back in 2019 and used the references to other libraries in their documentation to find more. For instance, Slate lists Draft.js, Prosemirror, and Quill as inspirations in their introduction document. Quill lists CKEditor, TinyMCE, Draft.js, Prosemirror, and Trix. Using this method, we gathered a list of more than ten candidates.
We went through the candidates and made a list of key library attributes using available documentation, blog posts, and other information we could find. Some key attributes that we collected across the board were:
- Beta or stable: It's not a showstopper if a library is in beta, but it's essential to know if using it introduces the risk of breaking changes.
- Stars on GitHub: This is not a quality measure by any means, but it gives you a feeling of the library's community and how active it is.
- React support: We built our app with React, so the ease of using the library in React is essential.
- Browser support: This was one of our main pain points with Slate
0.47.x, so we wanted to ensure that all major browsers on desktop and mobile had good support.
- Age of last commit: Like the age of the last release, this gives you a feeling of how active the community is, even if the latest release has aged, and a proxy for how quickly issues are fixed.
- Current version: If we ever looked back at our notes, we knew what version we had evaluated and see if things changed since then.
Typical showstoppers we identified:
- Missing or outdated support for React
- Missing mobile support (or no clear stance in that area)
- Hard to visually customize
- Missing extensibility or unclear documentation around extensions or plugins
- Small community or no recent updates
In addition to the key attributes, there were common features that we paid attention to across all libraries. The number grew as we looked at each library—we'd often find something that stood out in one library and see if other libraries supported it. For instance, the available extensions in tiptap satisfied most of our needs (including @-mentions) and began to compare that across libraries.
We exited this phase with a shortlist of five libraries: Slate, Quill, Prosemirror, ReMirror, and tiptap.
We didn't write a single line of code until this point, but it was time to start building quick prototypes to get a better feel for each library.
The goal of each prototype was to come as close as possible to the current state of the editor (visually and functionally). To keep each prototype around half a workday and come to an airtight statement:
- We used our existing frontend components (e.g., buttons, icons, CSS).
- We didn't implement custom extensions —if it wasn't a ready-to-use feature, we left it out (e.g., @mentions).
- We didn't implement interoperability with the current editor and its document model. Each prototype would only work standalone, with a new document from the library's documentation.
Eventually, we were able to compare a couple more things about our shortlist candidates:
- Developer experience
- Documentation quality
- User experience
With this experience, we listed the advantages and disadvantages of each of the libraries based on our needs. These might not be valid at the time you read this, or you might consider something that we identified as an advantage as a disadvantage (and vice versa).
We collected the data (including version numbers and GitHub stars) in May 2021. All conclusions have been drawn based on the library version listed next to the names. The libraries might have evolved significantly since then.
0.63.0 (20.6k ⭐)
We felt sure that migrating to the latest version was similar to starting from scratch. A significant disadvantage was that we couldn't have both
0.63.0 in our application simultaneously. Not using both simultaneously prevented us from gradually migrating features, and we'd need to do a complete rip and replace.
We liked that Slate was built for React (and didn't need a wrapper), had a large user base, was actively maintained, and had prior experience with it. However, it's still in beta, android support is experimental, and the maintainers still warn users about breaking API changes.
1.3.7 (29.9k ⭐️)
Quill is popular with well-known brands and officially maintained by Slab, a wiki we use for our internal documentation (and love!). Quill's main advantages were its popularity, its recommendations within our network, and that it had a stable release.
Quill itself doesn't offer any React support, and for our prototype, we used react-quill, a third-party dependency in beta. Although it's actively maintained, the current stable release is from 2019 (there's an ongoing effort for a new major version).
ProseMirror (5.7k ⭐️)1
We considered going directly with ProseMirror (a "toolkit for building rich-text editors on the web") and not a ready-to-use rich-text editor. ProseMirror would've given us the most control over the editing experience, but we decided the overhead of implementing our editor with the ProseMirror toolkit was too much work.
To us, Prosemirror stood out as the most powerful solution. We wouldn't have needed any third-party dependencies (except for a React integration) and would have been able to define our document schema from the ground up. We didn't go with it because it took a lot of code to get started (even the official ProseMirror guides state this) and defining our custom schema, while powerful, was not worth the effort since we didn't have many custom requirements.
We still liked ProseMirror, which led us to two available React integrations: ReMirror and tiptap.
1.0.0-next.60 (1.1k ⭐️)
We liked many things about ReMirror. It's a ProseMirror editor built on React and comes with extensions that covered most of our requirements.
It was hard not to go with ReMirror since we liked it as much as tiptap, but when evaluating both frameworks in May 2021, tiptap felt a little more mature.
Since we evaluated the rich text editor libraries, ReMirror made impressive progress. The library became stable, added many extensions, and the community grew. There's no longer much difference between ReMirror and tiptap, and it's a great example of how quickly a library can change. While we're still delighted with tiptap, today's ReMirror would have met our needs!
2.0.0-beta60 (10.8k ⭐️)
Tiptap ticked a lot of boxes for us. The library (like ReMirror) builds on top of ProseMirror. While it was created initially for Vue and relied on a third-party wrapper for React, it gained official react support in
2.0.0. Tiptap is in beta, so breaking changes are still possible, and the maintainers almost change things daily. Its API was the simplest of all libraries that we used in the prototyping phase, and we also liked that we could fall back to native ProseMirror code if necessary.
A library can work great in a sandbox environment, but integrating it with an existing codebase can surface issues. We wanted to find these issues as early as possible, so we implemented the first draft of Ashby's rich text editor with our favorite library, tiptap.
We paired the first draft with our usual writing process and technical specification to tease out more issues and tasks. The specifications covered motivations, goals, research, and a detailed roadmap of how we'd implement the new editor and make the switch in production.
I shared the specification and first draft implementation (via a pull request) with the team to get feedback.
Once we answered all questions and responded to all comments, it was clear tiptap would be the best solution for our needs and we were ready to use it as Ashby's new rich text library.
In part two, we'll share our migration plan from one library to the other and the challenges we faced executing that plan and releasing to production.