A rich text editor, sometimes referred to as a WYSIWYG (what you see is what you
get), is an essential part of many of today’s apps such as Linear, Notion,
Google Docs. Even experienced developers find choosing the right one a daunting
task that often leads to analysis paralysis. At Liveblocks, we’ve spent the last
year digging deep, providing solutions for some of the industry’s most popular
editors, and we’ve got opinions and facts, about each.

In this guide we’ll be exploring a number of different editors:
[Tiptap](#tiptap), [BlockNote](#blocknote), [Lexical](#lexical),
[Slate](#slate), [ProseMirror](#prosemirror), [Quill](#quill), [Plate](#plate),
[Remirror](#remirror), [Editor.js](#editor.js), [CKEditor](#ckeditor),
[TinyMCE](#tinymce).

## Introduction

In general, these editors fall into two categories: _core_ and
_batteries-included_. Your choice of editor will depend on whether you are
building a small feature like a single composer or a fully-fledged collaborative
editor with many custom features.

If you’re here for a quick recommendation, we think
**[the most well-rounded choice is Tiptap](#tiptap)** because it strikes a
balance between being a feature-rich editor without being overly opinionated.

Sometimes you want an opinionated editor that delivers what you need out of the
box, or a lightweight editor you can mold into your own. We encourage you to
read on to explore the potential drawbacks and advantages of other choices.

### Collaboration

At Liveblocks, we really care about collaboration. Most editors on this list
have realtime collaboration support thanks to a wonderful CRDT library called
[Yjs](https://yjs.dev/). When utilizing Yjs, you still need a backend service to
store your document and enable realtime collaboration.

[Liveblocks provides a general Yjs backend](/realtime-apis/yjs) that will work
with any editor that supports Yjs, and we also provide
[editor-specific integrations](/text-editor) for Tiptap and Lexical, with more
coming soon. These solutions mean you can get started without maintaining your
own back end.

Some editors also have their own operational transform-based solutions or a
closed-source cloud solution, which we’ll detail below.

### Before we start

For the scope of this article, we won’t include abandoned or unmaintained
editors like Draft.js or editors with small communities. We also won’t cover
closed-source editors like Froala. Additionally, when it comes to accessibility,
each editor will require some work especially if you add plugins, extensions, or
frontend frameworks into the mix. We’ll highlight any relevant documentation if
it exists.

---

## Tiptap

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/tiptap-logo.png"
    alt="Tiptap logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="ueberdosis" name="tiptap" />
  </span>
</Figure>

Tiptap is built on top of ProseMirror, a robust yet difficult to master editor.
Tiptap abstracts away the cumbersome parts of ProseMirror, leaving an enjoyable
developer experience. It also gives you the ability to easily extend its
functionality, including an interface into its core ProseMirror plugin system.

<div className="my-8 md:my-10">
  <div className="relative rounded-xl bg-marketing-surface-faded-subtle p-2 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
    <div className="relative aspect-video w-full overflow-hidden rounded-md after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
      <Embed
        src="https://nextjs-tiptap-advanced.liveblocks.app"
        className="absolute origin-top-left"
        style={{
          width: `${1.25 * 100}%`,
          height: `${1.25 * 100}%`,
          transform: `scale(${1 / 1.25})`,
        }}
      />
    </div>
  </div>
  <div className="markdown text-center text-xs! text-marketing-subtler *:mt-2! lg:text-sm!">
    Our [advanced collaborative text
    editor](/examples/collaborative-text-editor-advanced) built using Tiptap.
  </div>
</div>

Tiptap has a friendly MIT license and has out-of-the-box support from both
Liveblocks and Tiptap Cloud. While Tiptap is packed full of features, they are
divided into tree-shakable packages which keep the
[core](https://bundlephobia.com/package/@tiptap/core) bundle size smaller than
Quill, Slate, and Lexical.

### Extensions

[Extending Tiptap](https://tiptap.dev/docs/editor/extensions/functionality) is
easy, with the ability to add simple nodes, marks, custom commands, and
extensions. You can also override the behavior of other extensions, making it
simple to customize them to your needs without having to fork or rewrite from
scratch.

```tsx
// Adding some pre-built extensions
const editor = useEditor({
  extensions: [
    StarterKit, // Adds standard features, e.g. styles, quotes, lists
    Typography, // Adds smart typography, e.g. curly quotes, fractions
    liveblocks, // Adds realtime collaboration, comments, mentions
  ],
});
```

While most of Tiptap’s features are open-source and MIT-licensed, there are some
extensions and features called “pro” extensions which are not open source.

### Advanced customization

If you’re planning on a more advanced integration with Tiptap, you’ll most
likely need to get your hands dirty with ProseMirror, which has a steeper
learning curve. Tiptap’s command system can also take some time to get up to
speed with as it hides some complexity of ProseMirror’s transactions while
adding the ability to chain commands.

```tsx
// Toggle the current selection between <p> and <blockquote>
<button onClick={() => editor.chain().focus().toggleBlockquote().run()}>
  💬 Quote
</button>
```

Tiptap relies on a schema for its data model, but unlike ProseMirror, the schema
is generated for you by the extensions you add. Most implementations will never
need to touch or even know about the schema, but it can be extremely useful for
parsing and validation of your document.

### Drawbacks

Reading documentation between ProseMirror and Tiptap can be tricky at times, but
thankfully Tiptap’s source code is easy to read and has many
[examples](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-bold/src/bold.ts)
of working with ProseMirror’s core for more advanced plugins. Accessibility is
also somewhat poorly documented in Tiptap, whose guide essentially leaves it up
to the implementer to handle.

Another drawback is that Tiptap’s performance can slow down quite a bit if you
don’t follow a few best practices, such as avoiding traversing document state
during transactions and setting `shouldRerenderOnTransaction` to `false` when
rendering inside React.

### Server-side mutations

It’s not easy to run Tiptap on the server to mutate documents. For that, you’ll
want to use ProseMirror directly, which can be tricky after getting used to the
convenience of Tiptap. Another option is to simply modify the JSON directly. At
Liveblocks, we recently
[released a library](/docs/api-reference/liveblocks-node-prosemirror) to make
this a bit easier.

```ts
import { withProsemirrorDocument } from "@liveblocks/node-prosemirror";
import { Liveblocks } from "@liveblocks/node";

const client = new Liveblocks({ secret: "{{SECRET_KEY}}" });

// Update the document and return the text content
const text = await withProsemirrorDocument<string>(
  {
    client,
    roomId: "my-room-id",
  },
  async (api) => {
    // Get and execute a new transaction (tr)
    await api.update((doc, tr) => {
      return tr.insertText("hello");
    });

    return api.getText();
  }
);
```

### Realtime collaboration

Tiptap has extensions that support realtime collaboration backed by Yjs. There’s
also two official cloud solutions for adding realtime collaboration to your
editor, [Tiptap Cloud](https://tiptap.dev/) and
[Liveblocks Text Editor](/text-editor). Liveblocks Text Editor integrates into
all our other features, and includes realtime collaboration, live cursors,
comments, mentions, notifications, and version history with more coming soon.

<div className="my-8 md:my-10">
  <div className="relative rounded-xl bg-marketing-surface-faded-subtle p-2 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
    <div className="relative aspect-video w-full overflow-hidden rounded-md after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
      <Figure>
        <video autoPlay loop muted playsInline>
          <source
            src="/images/blog/choosing-the-right-text-editor-2025/tiptap-overview.mp4"
            type="video/mp4"
          />
        </video>
      </Figure>
    </div>
  </div>
  <div className="markdown text-center text-xs! text-marketing-subtler *:mt-2! lg:text-sm!">
    Our [Next.js Starter Kit](/nextjs-starter-kit) which uses Liveblocks Text
    Editor + Tiptap.
  </div>
</div>

<Comparison
  pros={[
    "Great documentation.",
    "Excellent realtime collaboration support.",
    "Framework-agnostic and React-specific packages.",
    "Highly extensible.",
    "Liveblocks and/or Tiptap Cloud integration.",
  ]}
  cons={[
    "Performance can slow down if you don’t follow best practices.",
    "Developing advanced features requires learning ProseMirror internals.",
    "Lacks headless server-side editing out-of-the-box.",
  ]}
/>

---

### Get started with Tiptap

If you’d like to build your collaborative Tiptap editor on Liveblocks, we have a
number of different ways to get started.

- [Get started guides](/docs/get-started/text-editor/tiptap)
- [Advanced example](/examples/collaborative-text-editor-advanced/nextjs-tiptap-advanced)
- [Basic example](/examples/collaborative-text-editor/nextjs-tiptap)

## BlockNote

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/blocknote-logo.png"
    alt="BlockNote logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="TypeCellOS" name="blocknote" />
  </span>
</Figure>

BlockNote is an opinionated, batteries-included, block-based editor (like
Notion) that extends both Tiptap and ProseMirror. If you’re looking for a
block-based editor, this is a great starting point. Most of the same pros as
Tiptap make this a solid choice.

<div className="my-8 md:my-10">
  <div className="relative rounded-xl bg-marketing-surface-faded-subtle p-2 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
    <div className="relative aspect-video w-full overflow-hidden rounded-md after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
      <Embed
        src="https://nextjs-yjs-blocknote-advanced.liveblocks.app/"
        className="absolute origin-top-left"
        style={{
          width: `${1.25 * 100}%`,
          height: `${1.25 * 100}%`,
          transform: `scale(${1 / 1.25})`,
        }}
      />
    </div>
  </div>
  <div className="markdown text-center text-xs! text-marketing-subtler *:mt-2! lg:text-sm!">
    Our [advanced collaborative text
    editor](/examples/collaborative-text-editor/nextjs-blocknote) built using
    BlockNote and Yjs.
  </div>
</div>

In addition to what you get from Tiptap, BlockNote provides many features
out-of-the-box, like a slash menu, floating formatting toolbar, slick
animations, and more. Unlike Tiptap, BlockNote is primarily focused on React.
While it’s possible to use it with vanilla JavaScript or other frameworks,
you’ll miss out on the benefits of the UI elements provided by BlockNote.

### Drawbacks

While BlockNote itself is completely free and open-source, some packages—such as
[docx](https://github.com/TypeCellOS/BlockNote/tree/main/packages/xl-docx-exporter)
and
[PDF exporters](https://github.com/TypeCellOS/BlockNote/tree/main/packages/xl-pdf-exporter)—require
a subscription for use in closed-source products.

### Realtime collaboration

BlockNote has realtime collaboration support with Yjs and Liveblocks. We also
have a full first-party integration coming soon, make sure to look out for that.

<Comparison
  pros={[
    "Based on battle-tested Tiptap and ProseMirror.",
    "Realtime collaboration with Yjs and Liveblocks.",
    "Block-based editing API and UI components out-of-the-box.",
  ]}
  cons={[
    "(Mostly) React-only.",
    "Heavier bundle size compared to more basic editors.",
  ]}
/>

[Check out our guide to getting started with Yjs, React, and BlockNote.](https://liveblocks.io/docs/get-started/yjs-blocknote-react)

---

## Lexical

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/lexical-logo.png"
    alt="Lexical logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="facebook" name="lexical" />
  </span>
</Figure>

Lexical has gained a lot of popularity lately because it’s backed by Facebook
(Meta) and used in their projects. We spent several months developing comments,
mentions, version history, and realtime collaboration support for Lexical, and
our takeaway is that although Lexical has a large community and commercial
backing, it needs more time to mature before we can recommend it over Tiptap.
This isn’t surprising, as at the time of writing, it hasn’t received a 1.0
release.

<div className="my-8 md:my-10">
  <div className="relative rounded-xl bg-marketing-surface-faded-subtle p-2 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
    <div className="relative aspect-video w-full overflow-hidden rounded-md after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
      <Embed
        src="https://nextjs-notion-like-ai-editor.liveblocks.app"
        className="absolute origin-top-left bg-white"
        style={{
          width: `${1.25 * 100}%`,
          height: `${1.25 * 100}%`,
          transform: `scale(${1 / 1.25})`,
        }}
      />
    </div>
  </div>
  <div className="markdown text-center text-xs! text-marketing-subtler *:mt-2! lg:text-sm!">
    Our [Notion-like AI
    editor](/examples/notion-like-ai-editor/nextjs-notion-like-ai-editor) built
    using Lexical.
  </div>
</div>

If you like Lexical, need comments, collaboration, or mentions while also
avoiding some of the growing pains we discovered,
[our package](/docs/ready-made-features/text-editor/lexical) is a good place to
start.

### Drawbacks

One of the main issues in extending Lexical is its lack of
[pure decorations](https://github.com/facebook/lexical/wiki/%5BRFC%5D-Reconciler-Decorations)—the
ability to style content without affecting the document itself. While Lexical
does have “decorator nodes,” they mutate the content of the document. This means
that features like collaborative cursors must calculate and draw HTML divs on
top of the text and listen to scroll/resize events to compensate.
[Slate](https://docs.slatejs.org/v0.47/slate-core/decoration) (Plate) and
[ProseMirror](https://prosemirror.net/docs/ref/#view.Decorations) (Tiptap,
Remirror, BlockNote) both have a pure decorations feature.

### Realtime Collaboration

Without the ready-made Liveblocks package, collaboration in Lexical is also
particularly difficult. While no editor’s collaboration features are perfect
(it’s a really difficult problem to solve well), there are still quite a few
issues that cause concern. Lexical’s collaboration implementation
[hardcodes](https://github.com/facebook/lexical/blob/07e300c60c4f1d7a5f63dd9ac983c1a0cf7133f0/packages/lexical-yjs/src/Bindings.ts#L58)
the name of the root node, making it impossible to have more than one Lexical
editor per Yjs document.

Some of the examples, like StickyNotes in the playground, get around this using
nested Lexical composers, instantiating new documents, and new
[WebSocket connections](https://github.com/facebook/lexical/blob/07e300c60c4f1d7a5f63dd9ac983c1a0cf7133f0/packages/lexical-playground/src/nodes/StickyComponent.tsx#L248C9-L248C22)
for each sticky note. This workaround would not scale in production. The good
news is that development is moving fast, and we’ve already seen many issues
resolved since we began integrating with Lexical.

### Extending Lexical

Lexical’s data structure is a hierarchy of nodes and each node is overridable
and customizable. You can also add your own nodes based on one of the 4 core
node types:

Lexical’s core is framework-agnostic, but it has a first-party React integration
with its own context, called
[LexicalComposer](https://lexical.dev/docs/packages/lexical-react), as part of
the `@lexical/react` package. Most of the core plugins are then reimplemented as
children of the LexicalComposer. Lexical also allows you to run headless on a
backend without DOM, using the `@lexical/headless` package. This is useful for
editing documents server-side.

### Server-side mutations

It’s possible to edit Lexical on the server using its core helper functions.
We’ve created [a library](/docs/api-reference/liveblocks-node-lexical) at
Liveblocks to make this simpler.

```ts
import { $getRoot } from "lexical";
import { $createParagraphNode, $createTextNode } from "lexical/nodes";
import { withLexicalDocument } from "@liveblocks/node-lexical";
import { Liveblocks } from "@liveblocks/node";

const client = new Liveblocks({ secret: "{{SECRET_KEY}}" });

await withLexicalDocument({ client, roomId: "my-room-id" }, async (doc) => {
  await doc.update(() => {
    // Adding a paragraph node with contained text node
    const root = $getRoot();
    const paragraphNode = $createParagraphNode();
    const textNode = $createTextNode("Hello world");
    paragraphNode.append(textNode);
    root.append(paragraphNode);
  });
});
```

<Comparison
  pros={[
    "Yjs realtime collaboration support.",
    "Framework-agnostic and React-specific packages.",
    "Liveblocks integration with comments, mentions, and version history.",
    "Very active development and a large community with Meta backing.",
  ]}
  cons={[
    "Yjs realtime collaboration support is a bit buggy without handling edge cases yourself.",
    "Lacks pure decorations, requiring DOM workarounds for advanced features.",
    "Heavier core package than Tiptap and Slate.",
  ]}
/>

[Check out our guide to getting started with Lexical.](https://liveblocks.io/docs/get-started/react-lexical)

---

## Slate

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/slate-logo.png"
    alt="Slate logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="ianstormtaylor" name="slate" />
  </span>
</Figure>

Slate is a customizable and powerful framework for creating rich text editors.
It’s used by Discord, Grafana, Sanity.io, Slite, and more. Slate provides
developers with complete control over the editing experience while maintaining
an intuitive design.

<div className="my-8 md:my-10">
  <div className="relative rounded-xl bg-marketing-surface-faded-subtle p-2 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
    <div className="relative aspect-video w-full overflow-hidden rounded-md after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
      <Embed
        src="https://nextjs-yjs-slate.liveblocks.app"
        className="absolute h-full w-full origin-top-left bg-white"
      />
    </div>
  </div>
  <div className="markdown text-center text-xs! text-marketing-subtler *:mt-2! lg:text-sm!">
    Our [collaborative text
    editor](/examples/collaborative-text-editor/nextjs-yjs-slate) built using
    Slate and Yjs.
  </div>
</div>

At Liveblocks, we also chose Slate as the default editor for our
[comments composer UI](https://liveblocks.io/docs/api-reference/liveblocks-react-ui#Composer)
component. We chose Slate because it supports a wide range of functionalities,
including rich text formatting, complete control of the editor, custom node
types, and an acceptable bundle size. Slate is vanilla JavaScript, but a React
package exists and can be integrated with other frameworks as well.

### Extending Slate

Slate has a very well documented system for extending its functionality. You can
define custom elements, transforms, styles, and events. While Slate’s core
feature set is not as complete as some other editors, it has a very extensive
list of
[examples](https://github.com/ianstormtaylor/slate/tree/main/site/examples) to
show you how to add those features on your own. If you want a more opinionated
and batteries-included version of Slate, [Plate](#plate) is an excellent option.

### Drawbacks

Slate’s bundle size is slightly larger than Tiptap and there aren’t quite as
many plugins/extensions in the ecosystem, leaving it up to you to implement some
features.

### Realtime collaboration

Slate can be made collaborative with external extensions such as
[slate-yjs](https://github.com/BitPhinix/slate-yjs/) and
[@liveblocks/yjs](https://liveblocks.io/docs/api-reference/liveblocks-yjs).

<Comparison
  pros={[
    "Great documentation.",
    "Yjs realtime collaboration support.",
    "Framework-agnostic and React-specific packages.",
    "Highly extensible.",
  ]}
  cons={[
    "Slightly heavier bundle size than Tiptap.",
    "Lacks out-of-the-box features.",
  ]}
/>

### Get started with Slate

[Check out our guide to getting started with Yjs, React, and Slate.](https://liveblocks.io/docs/get-started/yjs-slate-react)

---

## Quill

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/quill-logo.png"
    alt="Quill logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="slab" name="quill" />
  </span>
</Figure>

Quill is a powerful editor that has been used by many popular apps like Slack,
LinkedIn, Figma, Zoom, Miro, and Airtable. Quill has been stagnant in recent
years; however, its version 2, released in April 2024, is a rewrite that focuses
on fixing many of the issues which caused some developers to move on. Quill 2
has also been rewritten in TypeScript. Quill’s document model is called
Parchment, and users can use it to define their own “blots” that describe
attributes, blocks, embeds, and anything else. This is a similar concept to
ProseMirror’s schemas, nodes, and marks.

<div className="my-8 md:my-10">
  <div className="relative rounded-xl bg-marketing-surface-faded-subtle p-2 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
    <div className="relative aspect-video w-full overflow-hidden rounded-md after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-marketing-divider-subtle">
      <Embed
        src="https://nextjs-yjs-quill.liveblocks.app"
        className="absolute h-full w-full origin-top-left bg-white"
      />
    </div>
  </div>
  <div className="markdown text-center text-xs! text-marketing-subtler *:mt-2! lg:text-sm!">
    Our [collaborative text
    editor](/examples/collaborative-text-editor/nextjs-yjs-quill) built using
    Quill and Yjs.
  </div>
</div>

Quill is licensed under the permissive BSD-3-Clause license, making it ideal for
both personal and commercial use. Quill is backed by Slab, a commercial company,
and also has a very strong community on GitHub. Before April’s release, it had
only received a few updates in the previous few years. We’ll keep an eye on
Quill to see if its comeback is sustained.

Quill does not require a specific framework, so it can be seamlessly integrated
into various frameworks such as [React](https://quilljs.com/playground/react).

### Drawbacks

Like Lexical, Quill lacks pure decorations, which allow you to style content
without modifying the document model. This is useful for things like
search/replace, collaborative cursors, etc. An entire library called
`quill-cursors` exists just to overcome this limitation by placing DOM elements
on top of the editor.

### Realtime collaboration

Quill can be made collaborative by using Yjs and
[y-quill](https://github.com/yjs/y-quill) with an optional backend like
Liveblocks. Quill also has an Elixir-based operational transform backend called
[Delta](https://github.com/slab/delta-elixir).

<Comparison
  pros={[
    "Good documentation.",
    "Framework-agnostic and React-specific packages.",
    "Yjs realtime collaboration support.",
    "Simple delta format.",
  ]}
  cons={[
    "Fewer out-of-the-box features and plugins haven’t yet updated to Quill 2",
    "Lack of pure decorations.",
    "Somewhat less active development and a smaller community.",
    "Twice the bundle size of Tiptap or Slate.",
  ]}
/>

[Check out our guide to getting started with Yjs, React, and Quill.](https://liveblocks.io/docs/get-started/yjs-quill-react)

---

## ProseMirror

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/prosemirror-logo.png"
    alt="ProseMirror logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="prosemirror" name="prosemirror" />
  </span>
</Figure>

ProseMirror powers Tiptap, Remirror, BlockNote, and others. It is one of the
most battle-tested editors out there. ProseMirror relies on a schema for its
data structure and has a clear separation of concerns in its codebase. The main
ingredients of a ProseMirror editor are
[state](https://prosemirror.net/docs/ref/#state),
[view](https://prosemirror.net/docs/ref/#view),
[model](https://prosemirror.net/docs/ref/#model), and
[transforms](https://prosemirror.net/docs/ref/#transform), which are individual
packages that can be imported on their own. In addition to these modules, you
will also need a schema and at least the
[prosemirror-example-setup](https://github.com/prosemirror/prosemirror-example-setup)
just to get going.

### Drawbacks

To quote ProseMirror’s documentation directly:

[“Setting up a full editor ‘from scratch’, using only the core libraries, requires quite a lot of code.”](https://prosemirror.net/examples/basic/#:~:text=Setting%20up%20a%20full%20editor%20%E2%80%98from%20scratch%E2%80%99%2C%20using%20only%20the%20core%20libraries%2C%20requires%20quite%20a%20lot%20of%20code)

For this reason, unless you’re a purist, masochist, or both, we recommend
starting with one of the excellent ProseMirror-based editors such as Tiptap,
Remirror, or BlockNote. However, if you’re going to be modifying any of the
aforementioned editors, it’s definitely worth learning how ProseMirror works.
Thankfully, stellar [documentation](https://prosemirror.net/docs/) and an
[active community](https://discuss.prosemirror.net/) make this easier.

<Comparison
  pros={[
    "Excellent documentation with an active community.",
    "Yjs realtime collaboration support.",
  ]}
  cons={[
    "Requires a lot of code for a basic example.",
    "Fewer out-of-the-box features.",
    "Steeper learning curve.",
  ]}
/>

---

## Plate

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/plate-logo.png"
    alt="Plate logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="udecode" name="plate" />
  </span>
</Figure>

Plate is built on top of Slate and adds a host of features like UI, dozens of
prebuilt plugins, and entire templates. This is an impressive upgrade over Slate
if you’re looking for a more opinionated “batteries-included” editor.

Plate also maintains a large amount of flexibility with its plugins, so you can
choose to incorporate block-based editing, floating toolbars, mentions,
comments, and AI features. Most of these advanced features will require some
additional work to implement, such as a backend to store comments. While Plate
is free and open-source under the MIT license, it also offers a paid template
with lifetime access for a fixed price.

### Drawbacks

One drawback to Plate is that most of its functionality—such as plugins,
primitives, and components—is React-only. Another drawback is that, at the time
of writing, collaboration is only possible through Hocuspocus. However, there’s
no technical reason for this, and it wouldn’t be too difficult to add support
for other Yjs providers such as Liveblocks.
[Let us know](https://liveblocks.io/contact) if you’d like us to build a
compatible package backed by Liveblocks!

<Comparison
  pros={[
    "Extensive library of plugins.",
    "Server-side editing support.",
    "Templates to help you get started fast.",
  ]}
  cons={[
    "React-only.",
    "Collaboration is only available through Hocuspocus.",
    "Heavier bundle size compared to more basic editors.",
  ]}
/>

---

## Remirror

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/remirror-logo.png"
    alt="Remirror logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="remirror" name="remirror" />
  </span>
</Figure>

Remirror is a project very similar to Tiptap that takes a more opinionated and
batteries-included approach, with over 30 plugins, React hooks, i18n, and a11y
accessibility. It is built on top of ProseMirror, just like Tiptap, and has a
friendly MIT license.

It is one of the few editors that explicitly states its support of a11y and
accessibility. Now at version 3, it’s considered a stable and mature editor.

### Drawbacks

Drawbacks compared to Tiptap are that its community is a bit smaller, with
slower updates, and most of its functionality is React-only. Additionally, the
bundle sizes of Remirror and its extensions tend to be larger than Tiptap.

### Realtime collaboration

Remirror supports realtime collaboration with Yjs and Liveblocks through its
[`YjsExtension`](https://www.remirror.io/docs/extensions/yjs-extension/).

<Comparison
  pros={[
    "Great documentation.",
    "Extensive library of plugins.",
    "Collaboration with Yjs and Liveblocks.",
  ]}
  cons={[
    "React-only.",
    "Larger bundle size.",
    "Smaller community with less frequent updates.",
  ]}
/>

---

## Editor.js

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/editorjs-logo.png"
    alt="Editor.js logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="codex-team" name="editor.js" />
  </span>
</Figure>

Editor.js is a well-established block-based rich text editor that is actively
maintained with a large community. Editor.js offers many plugins (called Tools)
and a modern design. The data structure consists of blocks, inlines, and tunes.

The core API has a few extra features that other editors don’t have built-in,
such as tooltips. The editor itself is framework-agnostic, and there are many
community-made plugins and wrappers for almost every framework, as well as
backend frameworks and CMS integrations.

### Drawbacks

The biggest drawback to Editor.js is the lack of any kind of realtime
collaboration support, which we believe is crucial to most modern apps. There
are a few PRs and some unmaintained attempts at Yjs support, but nothing that’s
part of the core product. Due to its opinionated and feature-rich nature, the
package size is also quite large, even without the useful plugins.

### Realtime collaboration

Editor.js does not support realtime collaboration.

<Comparison
  pros={[
    "Feature-rich with an extensive library of plugins.",
    "Many community-made integrations with various CMS and backend frameworks.",
  ]}
  cons={["Larger bundle size.", "Lack of realtime collaboration support."]}
/>

---

## CKEditor

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/ckeditor-logo.png"
    alt="CKEditor logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="ckeditor" name="ckeditor5" />
  </span>
</Figure>

CKEditor has an impressive feature set, and the editor itself dates back more
than 20 years! The current version, CKEditor 5, is very modern and easy to use.
CKEditor also features a plugin system and support for Angular, React, Vue,
Next, and more. CKEditor is a solid choice as long as you are aware of the
drawbacks of a GPL-2 license, which may require you to open-source derivative
works. Of course, [CKSource](https://cksource.com/) sells you a non-GPL license
as well as cloud services for realtime collaboration. CKEditor’s cloud pricing
is based on “editor loads”.

### Drawbacks

Many of its features are behind a paywall, such as markdown, media embeds,
mentions, comments, and even multi-level lists. The extensive list of paywalled
features and restrictive license are a blocker to our recommendation. However,
if you need a white-glove solution and are willing to pay, it may be worth
looking into.

### Realtime collaboration

As far as we can tell, there is no other collaboration backend available besides
CKEditor’s proprietary solution.

<Comparison
  pros={[
    "Extremely feature-rich out-of-the-box.",
    "Works with many frameworks.",
  ]}
  cons={[
    "Locked into CKEditor’s cloud service for collaboration.",
    "GPL-2 license may be a blocker for some.",
    "Some features are paid with load-based pricing.",
  ]}
/>

---

## TinyMCE

<Figure highlight={false}>
  <Image
    src="/images/blog/choosing-the-right-text-editor-2025/tinymce-logo.png"
    alt="TinyMCE logo"
    width={672}
    height={378}
    quality={90}
  />
  <span className="absolute bottom-[14.81%] left-[8.33%]">
    <GitHubStars org="tinymce" name="tinymce" />
  </span>
</Figure>

Like CKEditor, TinyMCE is also over 20 years old and also under the GPL-2
license. Despite its age, it’s well supported and actively maintained. Their
cloud service also prices based on editor load. TinyMCE is available for vanilla
JavaScript, React, Vue, and Angular.

### Drawbacks

The list of features is comparable to CKEditor, and many of TinyMCE’s
plugins—such as markdown, mentions, comments, and even advanced typography—are
behind the paywall. We couldn’t find a way based on the documentation to modify
documents on the backend.

### Realtime collaboration

As far as we can tell, there is no other collaboration backend available besides
TinyMCE’s proprietary solution.

<Comparison
  pros={[
    "Extremely feature-rich out-of-the-box.",
    "Works with many frameworks.",
  ]}
  cons={[
    "Locked into Tiny Cloud for collaboration.",
    "GPL-2 license may be a blocker for some.",
    "Some features are paywalled with load-based pricing.",
  ]}
/>

---

## Comparison table

<Table scrollable>
  <TableHead>
    <TableRow>
      <TableCell subtle={false} header width={120} align="left">Editor</TableCell>
      <TableCell header width={200} align="left">Extended from</TableCell>
      <TableCell header width={200} align="left">Framework</TableCell>
      <TableCell header width={200} align="left">Collaboration</TableCell>
      <TableCell header width={200} align="left">Comments</TableCell>
      <TableCell header width={200} align="left">Mentions</TableCell>
      <TableCell header width={420} align="left">Server-side editing</TableCell>
      <TableCell header width={100} align="left">License</TableCell>
      <TableCell header width={100} align="left">Stars</TableCell>
    </TableRow>
  </TableHead>
  <TableBody>
    <TableRow>
      <TableCell width={120} align="left" subtle={false}>ProseMirror</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla</TableCell>
      <TableCell width={200} align="left">Yjs</TableCell>
      <TableCell width={200} align="left">No, but there are examples</TableCell>
      <TableCell width={200} align="left">suggestion plugin</TableCell>
      <TableCell width={420} align="left">Yes, prosemirror-state and prosemirror-model can be used to modify a document in NodeJs or using Liveblocks’ Node.js ProseMirror package</TableCell>
      <TableCell width={100} align="left">MIT</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="prosemirror" name="prosemirror" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Tiptap</TableCell>
      <TableCell width={200} align="left">ProseMirror</TableCell>
      <TableCell width={200} align="left" className="wrap-break-word">Vanilla, React, Vue, Svelte</TableCell>
      <TableCell width={200} align="left">Liveblocks, Tiptap Cloud, Yjs</TableCell>
      <TableCell width={200} align="left">Zero config with Liveblocks, Tiptap available, custom possible</TableCell>
      <TableCell width={200} align="left">Zero config with Liveblocks, Tiptap available, custom possible</TableCell>
      <TableCell width={420} align="left">Possible via ProseMirror or using Liveblocks’ Node.js ProseMirror package</TableCell>
      <TableCell width={100} align="left">MIT</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="ueberdosis" name="tiptap" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Remirror</TableCell>
      <TableCell width={200} align="left">ProseMirror</TableCell>
      <TableCell width={200} align="left">React</TableCell>
      <TableCell width={200} align="left">Yjs</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={420} align="left">Possible via ProseMirror or using Liveblocks’ Node.js ProseMirror package</TableCell>
      <TableCell width={100} align="left">MIT</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="remirror" name="remirror" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>BlockNote</TableCell>
      <TableCell width={200} align="left">ProseMirror, Tiptap</TableCell>
      <TableCell width={200} align="left">React</TableCell>
      <TableCell width={200} align="left">Yjs</TableCell>
      <TableCell width={200} align="left">Yes with Liveblocks or custom examples</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={420} align="left">Possible via ProseMirror or using Liveblocks’ Node.js ProseMirror package</TableCell>
      <TableCell width={100} align="left">MPL 2</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="TypeCellOS" name="BlockNote" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Lexical</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla, React, iOS, Others*</TableCell>
      <TableCell width={200} align="left">Liveblocks, Yjs</TableCell>
      <TableCell width={200} align="left">Zero config with Liveblocks, custom possible</TableCell>
      <TableCell width={200} align="left">Zero config with Liveblocks, custom possible</TableCell>
      <TableCell width={420} align="left">Possible using lexical/headless or Liveblocks’ Node.js Lexical package.</TableCell>
      <TableCell width={100} align="left">MIT</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="facebook" name="lexical" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Slate</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla, React</TableCell>
      <TableCell width={200} align="left">Yjs</TableCell>
      <TableCell width={200} align="left">No, but there are examples</TableCell>
      <TableCell width={200} align="left">No, but an example exists in the repo</TableCell>
      <TableCell width={420} align="left">No</TableCell>
      <TableCell width={100} align="left">MIT</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="ianstormtaylor" name="slate" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Plate</TableCell>
      <TableCell width={200} align="left">Slate</TableCell>
      <TableCell width={200} align="left">React Only</TableCell>
      <TableCell width={200} align="left">Hocuspocus (Yjs)</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={420} align="left">Yes</TableCell>
      <TableCell width={100} align="left">MIT</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="udecode" name="plate" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Quill</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla</TableCell>
      <TableCell width={200} align="left">Yjs</TableCell>
      <TableCell width={200} align="left">No, but there are examples</TableCell>
      <TableCell width={200} align="left">No, but there’s a third party library</TableCell>
      <TableCell width={420} align="left">No</TableCell>
      <TableCell width={100} align="left">BSD-3</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="quilljs" name="quill" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>Editor.js</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla</TableCell>
      <TableCell width={200} align="left">No, but some are working on it.</TableCell>
      <TableCell width={200} align="left">No, but a third party library exists.</TableCell>
      <TableCell width={200} align="left">No, but examples exist</TableCell>
      <TableCell width={420} align="left">No, but some third party examples exist</TableCell>
      <TableCell width={100} align="left">Apache 2</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="codex-team" name="editor.js" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>CKEditor</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla, React, Vue, Angular</TableCell>
      <TableCell width={200} align="left">Yes, with CKEditor Cloud</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={420} align="left">Yes</TableCell>
      <TableCell width={100} align="left">GPL-2+</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="ckeditor" name="ckeditor5" /></TableCell>
    </TableRow>

    <TableRow>
      <TableCell width={120} align="left" subtle={false}>TinyMCE</TableCell>
      <TableCell width={200} align="left" disabled>–</TableCell>
      <TableCell width={200} align="left">Vanilla, React, Vue, Angular, and more</TableCell>
      <TableCell width={200} align="left">Yes, with Tiny Cloud</TableCell>
      <TableCell width={200} align="left">Yes, with Tiny cloud</TableCell>
      <TableCell width={200} align="left">Yes</TableCell>
      <TableCell width={420} align="left">No</TableCell>
      <TableCell width={100} align="left">GPL-2+</TableCell>
      <TableCell width={100} align="left"><GitHubStars compact org="tinymce" name="tinymce" /></TableCell>
    </TableRow>

  </TableBody>
</Table>