The best React rich text editor for MDX outputs React, not JSON
An MDX editor gives you renderable source, not a document format you still have to parse. Draftbase ships a block-based MDX editor built on that idea. Write a block, publish it, and it's already a React-renderable string.
Updated
Simplified preview — the real editor renders full MDX.
What is a rich text editor for React, and why MDX changes the tradeoffs
A React rich text editor lets a writer format text inside a React app: headings, bold, links, embedded blocks. Most popular options store that formatting as a proprietary JSON document. Slate, TipTap, Lexical, and Draft.js all work this way. That document is internal to the editor. It isn't standard markup.
Ask which React rich text editor is best and most answers name a library. That's the wrong first question. Ask what format the content ends up in. The format decides how much work is left after the editor ships.
That JSON document is not directly renderable. Every team using one of these libraries writes a serializer. It walks the document tree and turns it into HTML or React elements. Change the schema and the serializer changes too. Add a callout, a video embed, or a code sandbox, and the serializer needs a new branch. The editor's flexibility becomes the renderer's maintenance burden.
This isn't a knock on those libraries. Slate, TipTap, and Lexical are good at what they're built for. The tradeoff is scope. They solve editing and leave rendering to whoever consumes the output.
MDX is a different kind of output. It's Markdown plus JSX. An editor built on MDX produces content that is already valid, renderable React source. A heading in MDX is a Markdown heading. A custom block is a JSX component, used like any other component in the app.
This is why an MDX-based editor travels well. The same string renders the same way in a blog, a docs site, or a marketing page. It isn't tied to one library's document model. Move it to a different React project and it still renders.
Take a writer embedding a pricing table in a blog post. A JSON-document editor needs a new node type, a UI affordance to insert it, and a serializer branch to render it. In MDX it's one line: <PricingTable plan="pro" />. That component already exists in the app.
The editor still has a learning curve. A writer needs Markdown basics — headings, bold, blockquotes. What's removed is the second curve. No frontend team has to learn a proprietary document schema to display what the writer typed — the only schema is the content type the template already defines, served through the same headless CMS delivery API.
How Draftbase's MDX editor works
Four pieces run end to end, from typing a heading to rendering it in an app. Each is small on its own. Together they remove the renderer-building step.
Block-based editing
Content is edited as discrete blocks (headings, callouts, paragraphs), not one long text stream. Structure stays visible while writing, and every block is selectable and reorderable.
Typed field validation
Every template field carries its own rules: required, length, pattern. All are enforced on write, so a bad entry never reaches the delivery API or a live page.
Drop-in editor
Writers get an intuitive editor out of the box. No setup, no separate CMS to host.
Renders via <MDXContent>
Stored MDX renders with @draftbase/renderer's MDXContent component. No custom parser to write or maintain.
Custom React components used inside content are registered separately, through the MDX components screen. Each component is defined once, with a typed set of props: string, number, boolean. A writer drops a named component into a block the way they'd type a heading. The field editor validates the props before the entry saves.
None of these four pieces require a separate build step. There's no compiler to configure, no plugin chain to assemble before content becomes usable. Write MDX in the editor, publish the entry, and it's ready for MDXContent to render.
A non-technical writer never sees this plumbing. They see blocks, a toolbar, and a save button. The schema and the component registry work in the background. Nobody learns MDX syntax by hand.
Templates and MDX components are both schema-defined, so edits aren't limited to the editor UI. An agent making MCP-connected edits reads the same schema a human editor fills in. One source of truth for what a valid entry looks like.
What ships in the editor
Nine things a writer can do without a developer opening the repo. Every one of them is in the editor today, not on a roadmap.
Block type switching
Paragraph, heading levels, and quote from one dropdown. The block type is part of the document, not a font size someone picked.
Markdown shortcuts
Type ## and get a heading, - and get a list. Writers who know Markdown never touch the toolbar.
Tables
Insert and edit GFM tables in place, with rows and columns added from the cell you're standing in.
Link dialog
Links get a dialog, including links to another entry — so an internal link points at a record, not a URL string that rots.
Image upload to the media library
Dropping an image uploads it through the media library and embeds the CDN URL. No pasted third-party link to break later.
Code blocks with highlighting
Fenced code blocks are edited in CodeMirror, with a language per block, so a docs page keeps its syntax highlighting.
Custom MDX components
Registered components appear in an insert menu with their typed props as a form. The props are validated before the entry saves.
Undo, redo, and revisions
Undo inside the session, revision history after it. Every save keeps a version, so a bad edit is a rollback.
Ask AI on a selection
Select a passage, ask for a rewrite, and review the change as a diff before it lands in the field.
Comparing React rich text editor approaches
Every approach to rich text in React sits on one spectrum. How much of the renderer do you build, and how much ships with the editor? The table lines up three common paths on the same four questions.
| Approach | Slate / TipTap / Lexical (bring your own) | Generic CMS rich text field | Draftbase MDX editor |
|---|---|---|---|
| Output format | Custom JSON schema | CMS-proprietary JSON | Plain MDX string |
| Renderer needed | You build one | You build one | Ships built-in via @draftbase/renderer |
| Embed custom React components | Only if you build that yourself | Rarely supported | Native. MDX is JSX |
| Setup cost | Library + schema + renderer, days of work | CMS account + custom parser | Drop-in editor, no separate hosting |
All four rows show the same pattern. Bring-your-own editors and generic CMS fields push the rendering problem onto the frontend team, later. An MDX editor folds that cost into the editor. You pay it once, not on every project that reuses the content.
Best rich text editors for React, compared
Five real options plus this one. The first four are editor libraries: you install them into an app you already own, and the content stays your problem. That is a feature if you want it, and the reason the last column is not a clean sweep.
| Editor | Output format | React components in content | Stores and serves the content | License and setup cost |
|---|---|---|---|---|
| Lexical | Its own JSON editor state | Custom nodes you write and register | No, bring your own | MIT, free. Compose plugins yourself |
| TipTap | ProseMirror JSON or HTML | React node views, per extension | No in the open-source core | MIT core, free. Extensions to assemble |
| Slate | A JSON document you define | Yes, you render every node | No, bring your own | MIT, free. Highest, you own the schema |
| Quill | HTML or Delta | Not natively, custom blots only | No, bring your own | BSD, free. Lowest, drops in fastest |
| MDXEditor (open source) | Markdown and MDX | JSX, via component descriptors | No, bring your own | MIT, free. Low, one package |
| Draftbase | Plain MDX string | Registered components with typed props | Yes: validation, revisions, delivery API | Hosted, free tier. No library to wire up |
Read the fourth column as the whole trade. The libraries win on control and price: MIT-licensed, free forever, running inside your app with no vendor between you and the content. Draftbase is a hosted product, so that independence is what you give up. What you get back is the four jobs the libraries hand you afterwards — storing the content, validating it, versioning it, and serving it to a frontend. If your app already has all four, install Lexical or TipTap and stop reading.
How to add a rich text editor to a React app
Two paths, and the difference isn't the editor. It's what you own after the writer clicks save.
Path 1: install an editor library
Fast to a working toolbar, then the real work starts. The editor hands you a document; where it goes, what makes it valid, and how it becomes React elements are all yours.
const editor = useEditor({ extensions: [StarterKit] });
// Everything past this line is yours to build:
await db.posts.update(id, { body: editor.getJSON() }); // storage
// + validation, revision history, draft/publish, and a
// renderer that walks that JSON back into componentsPath 2: use a CMS that ships the editor and the storage together
The editor lives in the dashboard, the content lives behind a read-only delivery API, and the frontend renders the stored MDX directly. Same markup as the React CMS path, because it is the same call.
import { createClient } from '@draftbase/sdk';
import { MDXContent } from '@draftbase/renderer';
const draftbase = createClient({ apiKey: process.env.DRAFTBASE_API_KEY! });
export default async function Post({ params }) {
const entry = await draftbase.getEntry(params.slug);
return <MDXContent source={entry.fields.body} components={{ Callout }} />;
}Nothing in the second sample parses the content. MDX compiles to JSX, so the framework's existing compiler does that step, and the components map is how your own React components render inside the prose a writer typed.
Common pitfalls with rich text in React
Teams underestimate the renderer-maintenance cost of a JSON rich text format. The editor library is easy to install. The serializer is the part that keeps needing updates. Every new block type a writer asks for is a new branch, shipped by an engineer rather than the writer who needed it.
A common shortcut is rendering CMS rich text with dangerouslySetInnerHTML. That's an XSS risk and a dead end for embedding real components. Injected HTML has no React component boundary. A callout box or an embedded chart becomes static markup at best, an injection vector at worst.
This shows up most often after migrating from a page-builder CMS. The old field stored HTML, the new field stores HTML, and dangerouslySetInnerHTML looks like the fastest way to ship. It works for plain paragraphs. It breaks on anything interactive, because client-side state never survives a round trip through raw HTML.
The fix is a format the framework already understands. That's what an MDX editor gives you by default. No serializer stands between what a writer types and what a browser renders.
A quieter third pitfall: two rich text formats across surfaces. Markdown in the docs site, a proprietary format in the blog. Now you maintain two renderers. MDX is a superset of Markdown, so standardizing on it collapses both into one.
Demand for rich, structured editing keeps growing
The global WYSIWYG editor software market is projected to grow from $236.09 million in 2026 to $416.33 million by 2035, a 6.4% CAGR. (Source) More teams adopting rich editing means more teams hitting the renderer-maintenance problem. A growing market doesn't shrink that problem. It just means more teams find it later, after the editor is wired into production.
MDX files compile to regular JSX at build or request time, per the MDX documentation. No separate runtime interprets them. That's why an MDX-based editor needs no bespoke rendering layer. The compiler step already exists in the framework.
So here's the practical takeaway. Ask what an editor outputs before asking what it can format. An editor that outputs MDX hands off work the framework already does. An editor that outputs a custom document format hands off a project.
Try the MDX editor for React
Write a block, embed a component, and render it with @draftbase/renderer in one sitting.
Hobby is free, no card. Startup is $49/mo when you outgrow it. The price is on the pricing page, where prices go.
No migration quarter, no kickoff workshop. Define a template and ship something today.
Frequently asked questions
What is the best rich text editor for React?
There's no single best option. It depends on what the output needs to become. Slate, TipTap, and Lexical are strong choices if you're willing to build a renderer for their JSON output. Draftbase's MDX editor skips that step: the output is already valid MDX your React app can render directly, so evaluating editors starts with the output format, not the feature list.
Does Draftbase support Markdown/GFM syntax like tables and strikethrough?
Yes. Draftbase content renders through next-mdx-remote with remark-gfm enabled, so GFM tables, strikethrough, and task lists work without extra configuration.
Can writers embed custom React components in content?
Yes — that's the core difference between MDX and plain Markdown. MDX allows JSX inline, so a writer can drop a custom component into a paragraph the same way they'd type a heading.
Do I need to write a custom renderer for MDX content?
No. Draftbase ships @draftbase/renderer, which exposes an <MDXContent> component that renders stored MDX strings directly. Per Next.js's own MDX documentation, MDX compiles to standard JSX, so no bespoke parser is required on top of it. The component handles fetching the entry and compiling its MDX field in one call.
Is an MDX editor harder to set up than a generic CMS rich text field?
No, it's less setup. A generic CMS rich text field still requires a custom renderer for its JSON output. Draftbase's editor is drop-in and requires no separate CMS to host, and the template schema doubles as the validation layer for every field a writer fills in.
What is the best WYSIWYG editor for React?
It depends on whether you want an editing surface or a content pipeline. If you only need editing inside an app you already own, Lexical and TipTap are the strongest WYSIWYG libraries in React right now, and both are free. If the content also has to be stored, validated, versioned, and served to a frontend, an editor library leaves those four jobs to you, and a CMS that ships the editor with the storage is less total code. Start by asking what happens to the output, not which toolbar looks best.
Can I use Lexical or TipTap with Draftbase?
Not as a replacement for the built-in editor — the Draftbase dashboard ships its own MDX editor and there is no plugin slot to swap it out. What you can do is write to the management API from your own interface, including one built on Lexical or TipTap, as long as what you send for a rich text field is a valid MDX string. The field stores MDX, so any editor that can serialize to Markdown or MDX can feed it.
How do I store rich text content from a React editor?
An editor library holds a document in memory; storing it is a separate problem you own. The usual path is to serialize the editor's document to a string or JSON blob, put it in your own database, and write a renderer that turns it back into React elements. The shortcut is to store rich text in a CMS field that already has a type, validation, revision history, and a read-only delivery API in front of it — then the editor's only job is producing the string.
How do I convert rich text to plain text?
Strip the markup rather than trying to hand-parse it: for Markdown/MDX, a library like remark with strip-markdown walks the AST and outputs the text nodes only, which handles nested formatting correctly. For a proprietary rich-text JSON format, you have to walk that document's own tree and concatenate its text nodes, since there's no shared library across CMS vendors. This is one of the costs of storing rich text as JSON instead of a portable format like MDX.
How do I convert HTML to rich text?
It depends what "rich text" means for your target. Converting HTML to Markdown/MDX is a solved problem — turndown or remark-parse plus rehype-remark do it directly. Converting HTML into a proprietary rich-text JSON schema (Contentful, Sanity, Payload) needs a bespoke mapper per vendor, since each defines its own node shapes. Storing content as MDX from the start avoids needing this conversion at all.