Visual editor bundle size benchmark, 2026
We measured the JavaScript weight of twelve visual and rich-text editor libraries under identical conditions. Craft.js is the lightest at 29.2 kB gzip; TinyMCE is the heaviest at 393.9 kB, a 13.5× spread across libraries that are routinely compared with each other.
TL;DR
- Across twelve editor libraries measured under identical conditions on 17 September 2026, minified and gzipped bundle sizes range from 29.2 kB (Craft.js 0.2.12) to 393.9 kB (TinyMCE 8.9.1) — a 13.5× spread.
- GrapesJS 0.23.6, the most widely embedded framework-agnostic page-builder engine, measured 294.6 kB gzip for the core alone; among rich-text editors, Quill 2.0.3 measured 58.7 kB with its themes and CKEditor 5 48.5.1 measured 161.2 kB with four plugins.
- The numbers exclude CSS and treat React as external for React-only libraries, because an application that chose one of them already ships React; the script that produced them is in the public repository.
What we measured
Every vendor quotes a bundle size, and almost none of them quote it the same way. Some numbers exclude peer dependencies, some exclude CSS, some are the size of a single entry point rather than the working import, and some are simply out of date. So we measured every editor we could install from npm the same way, on the same day, on the same machine: 12 libraries in the current run.
| Library | Package | Version | min+gzip | Raw | React external |
|---|---|---|---|---|---|
| Craft.js | @craftjs/core | 0.2.12 | 29.2 kB | 89.3 kB | yes |
| Quill | quill | 2.0.3 | 58.7 kB | 200.6 kB | no |
| Editor.js | @editorjs/editorjs | 2.31.7 | 64.3 kB | 237.9 kB | no |
| Puck | @measured/puck | 0.20.2 | 90.4 kB | 282.3 kB | yes |
| Lexical | @lexical/react | 0.50.0 | 104.9 kB | 317.6 kB | yes |
| Tiptap | @tiptap/react | 3.31.3 | 123.3 kB | 389.9 kB | yes |
| Plate | @udecode/plate | 48.0.5 | 150.8 kB | 500.5 kB | yes |
| CKEditor 5 | ckeditor5 | 48.5.1 | 161.2 kB | 629.9 kB | no |
| GrapesJS | grapesjs | 0.23.6 | 294.6 kB | 1115.7 kB | no |
| React Page | @react-page/editor | 5.4.6 | 377.2 kB | 1279.3 kB | yes |
| BlockNote | @blocknote/react | 0.54.2 | 386.1 kB | 1428.2 kB | yes |
| TinyMCE | tinymce | 8.9.1 | 393.9 kB | 1130.2 kB | no |
Run bundles-2026-09-17, measured 2026-09-17 with esbuild 0.25.10 on v22.19.0. CSS excluded.
Method
The method is deliberately boring, because the point is that you can repeat it.
- Each package is installed on its own, in its own directory, at the version
npm installresolves to on the day of the run. The resolved version is recorded next to each result. - A minimal entry file imports the library through its documented public entry point — the import the getting-started guide tells you to write, not a hand-picked deep import that flatters the number.
- esbuild bundles it: production mode, minified, tree-shaken,
platform: browser,target: es2020,NODE_ENV=production. - The output is gzipped at level 9 and the compressed byte count is recorded.
React and react-dom are marked external for the libraries measured through their React packages:
Puck, Craft.js, Plate, Tiptap, Lexical, React Page and BlockNote. That choice matters: including React
would add roughly the same fixed cost to each and tell you nothing about the editors, while a team
adding an editor to an existing React app genuinely does not pay it twice. The framework-agnostic
engines — GrapesJS, Editor.js, CKEditor 5, TinyMCE and Quill — get no exclusion, because they carry
everything they need.
Peer dependencies an editor cannot run without are bundled in. That is why BlockNote's figure includes
Mantine: its documented quick start renders through @blocknote/mantine. BlockNote also needed
--legacy-peer-deps, because a plain npm install of its documented packages failed with ERESOLVE;
the flag is recorded next to its result.
CSS is excluded from all measurements. Several of these libraries ship substantial stylesheets, and folding those into a JavaScript number would penalise them for shipping a design instead of making you write one. For TinyMCE that includes its skin modules, which are stylesheets wrapped in JavaScript.
What the numbers mean in practice
A 13.5× spread does not mean the same difference in value. Craft.js at 29.2 kB does not include an editor UI — you build the panels, the toolbar and the settings forms yourself. GrapesJS at 294.6 kB includes all three. Much of the difference is code you would otherwise write and ship anyway, so the honest comparison is not library against library, but library plus your code against library plus your code.
The editor route is not the marketing page. These bundles load behind authentication, on a
route users deliberately opened, usually once per session. A 294.6 kB editor chunk on a lazily
loaded /editor route is a very different proposition from 294.6 kB on a landing page, and any
performance conversation that skips that distinction is not a useful one.
Code splitting is not optional at the top of this range. Anything above roughly 100 kB gzip
should be loaded with a dynamic import so it never enters your main bundle. In Next.js that means
next/dynamic with server rendering disabled, which these editors need regardless because they
mount against a live DOM.
Version drift is real. Plate resolved to 48.0.5 during this run even though the registry's
latest tag pointed at 49.0.0, because npm's resolution respected a constraint in the install tree.
We report the version that was actually measured rather than the version we intended to measure —
which is the kind of small discrepancy that quietly invalidates most published benchmarks.
Limitations of this benchmark
A number is only useful with its caveats attached, so here are ours.
The measurement covers the initial import, not everything a product ends up loading. Plugins, themes, locales, icon fonts and the block libraries you write all add to it, and for a mature GrapesJS integration the plugins can exceed the core.
It is a single run on a single machine with a single bundler. A different bundler with different
tree-shaking, or a build with sideEffects configured differently, will produce different numbers —
usually within a few percent, occasionally not.
It says nothing about runtime performance. A small bundle can be slow and a large one can be fast; for the closest thing we have to a speed comparison, see the time to first editor benchmark, which measures how long each library takes to put an editor on screen.
Commercial SDKs are absent because their editors load from vendor infrastructure behind a licence key, so there is no equivalent import to measure. That is a limitation of the comparison, not a statement about those products.
How to cite this
EditorStack (2026). Visual editor bundle size benchmark, 2026. Measured 17 September 2026. https://www.editorstack.cc/research/bundle-size-benchmark-2026
The underlying data is available as JSON and CSV under CC BY 4.0, and the raw measurement
file lives at data/measurements/bundles.json in the repository. Full citation formats, including
BibTeX, are on the citation page.
Per-library notes
Numbers without context are how benchmarks mislead. Here is what sits behind each measurement.
Craft.js — 29.2 kB (v0.2.12). The smallest figure in the set, and the least comparable: Craft.js ships no editor interface, no style system and no HTML output. What you are measuring is a node tree with drag-and-drop. Any product built on it adds an interface that the other libraries include in their number.
Editor.js — 64.3 kB (v2.31.7). Core only, with no block tools installed. A realistic document editor adds image, list, table, quote and embed tools, and each one adds to this. Treat 64.3 kB as the floor for a bare editing surface rather than a shipping figure.
Puck — 90.4 kB (v0.20.2). Editor and renderer exports together, with React external. Worth noting that a production Puck integration usually ships only the renderer on public pages — a much smaller subset — so the number that reaches your visitors is lower than the number here.
Plate — 150.8 kB (v48.0.5). The editor factory alone. Plate's model is plugins plus copied-in components, so a realistic editor with tables, mentions and a toolbar is materially larger. This is the measurement that understates real-world weight most.
GrapesJS — 294.6 kB (v0.23.6). The full engine: canvas, component tree, style manager, device manager, asset manager, panels and commands. It is large because it is a complete editor rather than a foundation, and the CSS it ships is excluded from this figure.
React Page — 377.2 kB (v5.4.6). The largest of the page builders, driven by a Material UI-based
editor interface you cannot opt out of. The version is unchanged from the previous run; the 1.7 kB
increase came from its dependencies. It is also the only library in the set that would not bundle at
all against React 19 without an alias for react/jsx-runtime.js, recorded in our
time to first editor benchmark.
Quill — 58.7 kB (v2.0.3). The default quill entry, which registers the standard formats, the
toolbar, syntax and table modules and both the Snow and Bubble themes. Unusually, that is close to a
realistic configuration rather than a floor, because the interface is already in the number.
Lexical — 104.9 kB (v0.50.0). The composer with the rich-text and history plugins, React external. Up from 97.1 kB at 0.49.0 in the previous run.
Tiptap — 123.3 kB (v3.31.3). The React binding plus StarterKit, React external. Down from 125 kB at 3.30.2 in the previous run. No interface is included, because Tiptap ships none.
CKEditor 5 — 161.2 kB (v48.5.1). ClassicEditor with the Essentials, Paragraph, Bold and Italic
plugins, imported from the ckeditor5 aggregate package. That includes a working toolbar; every
further feature plugin adds to it.
BlockNote — 386.1 kB (v0.54.2). The documented quick start: useCreateBlockNote with the
Mantine-based BlockNoteView, Mantine bundled, React external. It includes the complete block-editing
interface. We did not measure the shadcn or Ariakit views.
TinyMCE — 393.9 kB (v8.9.1). The heaviest in the set: the self-hosted core plus the default icons, Silver theme and DOM model that the bundling guide requires, with no plugins and the skin modules excluded under the CSS rule.
What we deliberately did not measure
CSS. Several of these libraries ship substantial stylesheets and others expect you to write your own. Folding stylesheets into a JavaScript number would penalise the libraries that give you a design.
Plugins. A GrapesJS integration with six plugins is a different product from the core, and the plugin set differs per team. Measuring the core is the only comparable baseline.
Commercial SDKs. Builder.io, Plasmic, Unlayer, Beefree, Stripo, Topol and the GrapesJS Studio SDK load editors from vendor infrastructure behind licence keys. There is no equivalent local import, so there is no honest way to place them on this chart. Their absence is a limitation of the method, not a statement about the products.
Runtime performance. Bundle size is what the network pays; it says nothing about what the CPU pays. The time to first editor benchmark is the closest thing we have to that measurement.
How to use these numbers in a decision
Three practical rules come out of this data.
Compare like with like, then adjust for what you must build. Craft.js at 29.2 kB plus your editor interface is not obviously smaller than Puck at 90.4 kB with one included. Add the code you will write before comparing.
Put anything above roughly 100 kB behind a dynamic import. All of these editors mount against a
live DOM, so they cannot server-render anyway. In Next.js that means next/dynamic with SSR
disabled, on a route that is not your landing page.
Measure your own build before you argue about it. The script that produced this table takes about two minutes to run against your actual dependency set, and your numbers will differ from ours as soon as you add a plugin. Ours are a baseline, not a prediction.
Reproducing this
git clone https://github.com/GoodPHP/gjs-comparison
cd gjs-comparison
node scripts/measure-bundles.mjs # writes data/measurements/bundles.json
node scripts/measure-bundles.mjs --verify # re-measures, exits non-zero on driftThe --verify mode is what keeps this article honest: it re-installs every package, re-measures, and
fails if any published number has moved. Our build runs the same cross-check against the YAML dataset,
so a figure in the table cannot drift away from the run that produced it.
Frequently asked questions
- Why is GrapesJS so much larger than Craft.js?
- They do different amounts of work. Craft.js supplies a drag-and-drop node tree and expects you to build every piece of UI; GrapesJS ships a canvas, a style manager, a device manager, an asset manager, panels and a command system. The size difference is roughly the size of the product you would otherwise write yourself.
- Do these numbers include CSS?
- No. Each measurement is JavaScript only, because folding a stylesheet into a JavaScript figure makes libraries that ship CSS look worse than libraries that make you write it. GrapesJS, React Page, CKEditor 5, TinyMCE, BlockNote and Quill all ship stylesheets on top of the numbers here.
- Is React included in the React library measurements?
- No. React and react-dom are marked external for the React libraries — Puck, Craft.js, Plate, Tiptap, Lexical, React Page and BlockNote — on the basis that an application choosing a React editor already ships React. Framework-agnostic engines get no such exclusion, which is the fair comparison for a team deciding what to add to an existing app.
- How can I reproduce this?
- Clone the repository and run node scripts/measure-bundles.mjs. Adding --verify re-measures and exits non-zero if any number has drifted from the published run, which is also how our CI keeps the article honest.
Sources
- measure-bundles.mjs — the script that produced these numbers — accessed 2026-09-17
Written by the EditorStack Research Team. How we test and what we refuse to publish is on the methodology page.