React Markdown Kit editor vs MDXEditor
MDXEditor is a React component for editing Markdown and MDX,
built on Lexical, MIT licensed, with a plugin for every feature. This page compares it
with @react-markdown-kit/editor, which is also built on Lexical and edits Markdown
only.
The short version: if your content is MDX with JSX components in it, use MDXEditor. If your content is Markdown and the files must come back unchanged, read on.
Facts about MDXEditor come from its documentation and its repository. Facts about this editor link to the code or the test that backs them.
| MDXEditor | React Markdown Kit editor | |
|---|---|---|
| Content | Markdown and MDX, including JSX components | Markdown: CommonMark, plus GFM through a preset |
| Editing engine | Lexical | Lexical |
| Value in and out | Markdown string | Markdown string |
| Composition | Plugins passed in a plugins array | A preset shared with the renderer, plus extensions |
| Round trip | No published byte-identity corpus found | 22 of 22 byte-identical (test) |
| Gzipped JS for the entry | 163.1 KB (4.2.5) | 110.3 KB (0.1.0) |
| Server rendering | Client component | Client component; the renderer is a separate package that server-renders |
| Licence | MIT | MIT |
Editing model
Both editors put a Lexical surface in front of the user, and both keep the document as Markdown in your state.
MDXEditor composes features as plugins: headingsPlugin(), listsPlugin(),
tablePlugin(), jsxPlugin() and so on, passed in a plugins array. Nothing is on
until you turn it on, and the toolbar is itself a plugin.
<MDXEditor markdown={value} onChange={setValue} plugins={[headingsPlugin(), listsPlugin()]} />
React Markdown Kit composes the dialect instead, in a preset that the renderer and the editor share, so the editor writes exactly what your pages read.
import { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'
const appMarkdown = defineMarkdownPreset({ extensions: [gfm()] })
<MarkdownEditor preset={appMarkdown} value={value} onChange={setValue} />
The editing surface then follows the dialect. There is no list of features to keep in sync with the renderer, because there is one preset and one parser for both.
The practical difference: MDXEditor gives you finer control over which editing features
exist; this kit gives you a guarantee that preview and production render the same way,
since preview delegates to @react-markdown-kit/renderer rather than a second renderer.
Output format
Both hand back a Markdown string, so neither locks your content into editor JSON.
The difference is what counts as valid content. MDXEditor accepts MDX: JSX elements,
import and export statements, and expressions, with JSX handled by its JSX plugin
and described by a component descriptor you supply. This kit has no MDX support and no
plan for one. A JSX element in the source is not a component here; it is an HTML block,
preserved as an opaque node and written back as the bytes it came from.
If your documents are .mdx files with components inside them, that is the deciding
line and MDXEditor is the right tool.
Round trip
This is the section the kit exists for.
Open a document, switch modes, close it without typing. The file must be identical. The kit keeps the original source and the byte span of every top-level block, and writes unchanged blocks back from those bytes instead of re-serializing them.
The corpus is 22 documents that a line-oriented editor damaged on save, from
docs/AUDIT.md:
- Editor layer, bytes identical: 22 of 22, with GFM on and with GFM off
(
packages/editor/tests/roundtrip.test.ts). - Serializer layer, meaning unchanged and idempotent: 22 of 22; bytes identical
14 of 22, because that path canonicalizes on purpose
(
tests/roundtrip.test.ts).
We found no equivalent published corpus for MDXEditor, so this table says "not found",
not "fails". The fixture is plain JSON with a cases array of { name, source, why },
and nothing in it is specific to this package, so you can run it against MDXEditor in an
afternoon and publish the result.
How to run the suite on your own documents
Bundle
Measured by scripts/compare-bundles.mjs
and stored in docs/data/bundle-sizes.json,
on 2026-09-20. Each row is the whole import closure of the recorded entry,
bundled with esbuild 0.27.7, minified, tree-shaken, with React and React DOM
external. 1 KB is 1024 bytes.
| Package | Version | Minified | Gzipped |
|---|---|---|---|
| @react-markdown-kit/editor | 0.1.0 | 349.7 KB | 110.3 KB |
| @mdxeditor/editor | 4.2.5 | 511.3 KB | 163.1 KB |
That is 52.8 KB less gzipped JavaScript for the kit's entry. Two caveats keep the number
honest. The kit's row includes @react-markdown-kit/renderer, which is bundled in
because the editor imports it. Neither row includes CSS: both packages ship an optional
stylesheet that the measured entry does not import, and the generator records CSS
separately when a bundle emits any.
The row that matters more for most applications is the one that is not here. A page that only displays Markdown imports the renderer and never loads the editor: 36.8 KB gzipped for CommonMark, 48.5 KB with the GFM preset. The renderer
Server rendering
Neither rich surface server-renders. Selection, undo history and keyboard handling need
a DOM, so both components are client components, and MDXEditor's Next.js instructions
load it through dynamic() with ssr: false.
Where the two differ is what a server page has to import. In this kit the renderer and
the editor are separate packages with separate installs, so a server-rendered document
page imports @react-markdown-kit/renderer, carries no "use client" boundary, and
pulls no Lexical into the server bundle.
Server rendering
There is also a Node path for the editor itself, with no DOM at all:
import { createMarkdownBridge } from '@react-markdown-kit/editor'
const bridge = createMarkdownBridge({ preset: appMarkdown, headless: true })
bridge.load(source)
const saved = bridge.getMarkdown()
That is the pipeline the React editor runs, on @lexical/headless. It is how the round
trip is tested, and it is available for a migration script or a content check in CI.
Lexical versus Lexical
Both editors chose the same engine, so the engine is not the difference. Two things about how each wraps it are.
Where Markdown is parsed. This kit parses with micromark into mdast, the same
pipeline the renderer uses, and keeps mdast as the document representation.
@lexical/markdown and its line-oriented transformer protocol are not used anywhere in
the package, because that protocol is the root cause listed in the audit.
Whether Lexical is in your types. Nothing the root entry exports requires a Lexical
type. A test asserts that src/index.ts and src/types.ts import nothing from lexical
or @lexical/*
(test). The opt-in
@react-markdown-kit/editor/lexical entry is the exception, for extension authors. The
single escape hatch, editor.getNativeEditor(), returns
unknown, so you cast it yourself and the engine never leaks into your types by
accident. That keeps the door open to replacing the engine later without a breaking
change to the rest of the API.
How the kit wraps Lexical
Migration
The component boundary is small, so the swap is mostly mechanical.
// before
import { MDXEditor, headingsPlugin, listsPlugin, quotePlugin } from '@mdxeditor/editor'
import '@mdxeditor/editor/style.css'
<MDXEditor markdown={value} onChange={setValue} plugins={[headingsPlugin(), listsPlugin(), quotePlugin()]} />
// after
import { MarkdownEditor } from '@react-markdown-kit/editor'
import { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'
import '@react-markdown-kit/editor/styles.css'
const appMarkdown = defineMarkdownPreset({ extensions: [gfm()] })
<MarkdownEditor preset={appMarkdown} value={value} onChange={setValue} />
Four things to check before you commit:
- MDX content. If any document contains a JSX element you expect to keep editing as a component, stop here. It will be preserved as an opaque block, not edited.
- Plugins to preset. The feature plugins have no one-to-one mapping. Decide the
dialect instead: CommonMark, or CommonMark plus
gfm(), plus any extension you render with. Presets - Toolbar. The default toolbar is on by default here.
toolbar={false}removes it, and a render prop keeps the command items while you supply the markup. Editor basics - Image uploads.
onUploadImage(file, { signal, documentKey })resolves with{ src, alt, title }. Your storage, your endpoint. Images
Run the round-trip script over your content directory first. It tells you in one pass whether the new dialect reads everything you already have. The script
Who should stay with MDXEditor
- Your content is MDX, or you want JSX components editable in place.
- You want to choose each editing feature independently through plugins, including turning most of them off.
- You rely on a feature this kit does not have, such as its diff and source view plugin set or its sandpack code blocks.
Who should try this one
- Your content is Markdown files or Markdown in a database, and a diff of only what the author changed matters to your team.
- You already render Markdown with React and want the editor to write exactly that dialect.
- You want the editor package out of the server bundle and out of the pages that only display content.
- You want the round trip to be a test you can run, not a promise.
FAQ
Is there an alternative to MDXEditor for React?
Yes. @react-markdown-kit/editor is a rich, source and preview Markdown editor for React, also built on Lexical. It edits CommonMark and GFM, not MDX, and it asserts byte identity for 22 audited documents opened and saved without an edit.
Does React Markdown Kit edit MDX and JSX components?
No. It edits Markdown. MDXEditor parses and edits MDX, including JSX components and imports, which is what its name and its JSX plugin are for. If your content is MDX, that is a reason to stay with MDXEditor.
Which editor bundle is smaller?
The measured import closure of @react-markdown-kit/editor 0.1.0 is 110.3 KB gzipped against 163.1 KB for @mdxeditor/editor 4.2.5, a difference of 52.8 KB, with React external. Both rows come from scripts/compare-bundles.mjs.
Can either editor render on the server?
Neither rich editing surface renders on a server, because both need a DOM for selection and keyboard handling. React Markdown Kit splits the packages, so a server-rendered page that only displays Markdown imports the renderer and never loads editor code or Lexical.
Next
Try the editor demo · @react-markdown-kit/editor on npm · @mdxeditor/editor on npm · Source on GitHub
React Markdown editor · Lossless Markdown editing · Compared with Milkdown · Lexical Markdown editor guide