Skip to main content

React Markdown Kit vs react-markdown

Both turn a Markdown string into React elements, both parse with micromark, and both refuse to use dangerouslySetInnerHTML. react-markdown is the one almost everyone already has. This page compares them section by section, in the same order used on every comparison page here: install size, GFM, security defaults, streaming, server rendering, plugins, migration.

Facts about react-markdown come from its repository, remarkjs/react-markdown, at version 10.1.0. Facts about the kit link to the test or the generated table that produced them.

The short version: stay on react-markdown if it works and you care about the last kilobyte. Look here if you stream Markdown from a model, render the same document more than once, or want the editor and the plugins to read what the renderer reads. Read the alternative page for the three differences in full.

Install size

Bytes a browser downloads for one import, measured from published tarballs by scripts/compare-bundles.mjs and committed to docs/data/bundle-sizes.json on 2026-09-20. React and React DOM are external in every row, minification is esbuild with NODE_ENV set to production, and 1 KB is 1024 bytes.

One import, minified and gzipped
ImportVersionMinifiedGzippedLicensePeer react
react-markdown, CommonMark10.1.0115.0 KB35.5 KBMIT>=18
@react-markdown-kit/renderer, CommonMark0.1.0118.5 KB36.8 KBMIT>=18
react-markdown + remark-gfm10.1.0152.6 KB46.4 KBMIT>=18
@react-markdown-kit/renderer + GFM preset0.1.0158.1 KB48.5 KBMIT>=18

react-markdown wins this section. It is 1.3 KB smaller gzipped on CommonMark and 2.1 KB smaller with GFM on both sides. Size is not a reason to switch renderers.

GFM

react-markdown parses GitHub Flavored Markdown when you pass the remark-gfm plugin, which its readme documents. The kit has the same route and a native extension.

// react-markdown
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

<Markdown remarkPlugins={[remarkGfm]}>{content}</Markdown>
// React Markdown Kit
import Markdown, { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'

const preset = defineMarkdownPreset({ extensions: [gfm()] })

<Markdown preset={preset}>{content}</Markdown>

Both routes are asserted to agree. tests/gfm.test.ts runs 68 cases derived from the GFM spec three ways: through the native gfm() extension, through remarkPlugins={[remarkGfm]}, and through plain CommonMark. The first two must agree after normalizing attribute order, entity spelling and whitespace between block tags (tests/helpers/html.ts) and the third must not produce the GFM markup, so a case that would pass without GFM cannot sit in the corpus unless it is one of the 12 cases that assert something is not GFM, which are named in the test so the exemption cannot spread. tests/compatibility.test.tsx compares remark-gfm output between the two packages directly, and the four remarkPlugins cases match.

GFM on this page, rendered by @react-markdown-kit/renderer
Markdown
| Package | Gzipped |
| --- | ---: |
| renderer | measured |

- [x] task list
- [ ] ~~strikethrough~~ and https://example.com
Rendered
PackageGzipped
renderermeasured

Even. The extension saves a dependency; the plugin route is there if you prefer it.

Security defaults

The defaults are the same, and that is the measured result rather than a claim.

Defaultreact-markdown 10.1.0React Markdown Kit
dangerouslySetInnerHTMLNot used. The readme leads with it.Not used.
Raw HTML in the sourceRendered as visible text; rehype-raw to opt inSame, and skipHtml defaults to false in both
javascript: in a linkEmptied by the default urlTransformSame algorithm, same result on every probe URL
Plugins and componentsTrusted code, not sandboxedTrusted code, not sandboxed

Evidence on this side: the renderer security tests cover raw HTML, script tags, javascript: URLs, obfuscated protocols, image sources, relative URLs, allowedElements, and the policy applying to a precompiled document so it cannot be bypassed. The urlTransform and skipHtml rows of docs/COMPATIBILITY.md are the cross-package proof: 4 of 4 and 4 of 4 identical. The policy is described in the security model.

Both readmes say the same thing about plugins: a remark plugin, a rehype plugin or a component you pass in is your code, and rehype-sanitize is the answer when the content is not yours.

Even.

Streaming

A model writes Markdown a token at a time, so a chat UI hands the renderer a new prefix on every token and most prefixes are not valid Markdown.

React Markdown Kit passes. packages/renderer/tests/streaming.test.tsx is 59 tests that feed seven documents token by token and assert four properties on every prefix: no prefix throws, the HTML of the closed prefix is byte-identical at every later prefix, nothing in the closed prefix is duplicated, and the final prefix renders exactly like the whole document rendered once. The same run is repeated through compileMarkdown and through a mounted React root that grows token by token.

The cases are an unclosed code fence, half-written emphasis and strong, a table mid-row, a list mid-item, a heading with no trailing newline, a link with an unclosed bracket, and a fenced block that closes late. Half-written constructs render as the characters typed so far: **str is the text **str, not a broken element. Two prefixes legitimately rewrite output that already rendered, and both are pinned by tests rather than hidden: a paragraph becomes a heading when a setext underline arrives, and a GFM autolink points at the truncated host while the URL is still being typed. Both are CommonMark and GFM behaving correctly.

This repository runs no streaming tests against react-markdown, and its readme documents no streaming-specific behaviour, so nothing is claimed here about how it behaves under partial input. If it matters to you, run the same corpus yourself.

Kit, on the evidence in this repository.

Server rendering

Both render on the server with no DOM. The kit's own suites call renderToStaticMarkup throughout, including the CommonMark conformance suite, and scripts/pack-check.mjs installs the packed tarball into a consumer with no Lexical and renders there, so the renderer entry pulls in no browser-only code.

The difference is asynchronous work. react-markdown 10 exports MarkdownAsync and MarkdownHooks so a plugin can await something. The kit has neither and is synchronous only, which is listed as a difference in docs/COMPATIBILITY.md.

The kit adds a second path: compile once, render many times. compileMarkdown returns a MarkdownDocument that <Markdown document={...} /> renders without parsing again. On the reference machine in benchmarks/README.md a 10 KB document takes 16.80 ms from a string and 5.99 ms from a precompiled document, 2.8x faster, because parsing is about two thirds of the work.

Speed on one render is the other way round. Both sides parsing GFM, same process, median after five warm-up calls: 1 KB is 1.21x slower than react-markdown (2.69 ms against 2.23 ms), 10 KB is at parity (16.22 ms against 16.25 ms), and 100 KB is 0.89x (206.96 ms against 233.36 ms). The 1 KB row is an open regression: it is fixed per-render cost, and the fix, caching the processor per resolved configuration, is tracked in benchmarks/README.md and not done. Reproduce with pnpm bench.

react-markdown for async plugins. Kit for repeated renders of the same document.

Plugins

react-markdown takes remarkPlugins, rehypePlugins and remarkRehypeOptions. So does the kit, and the compatibility run compares them directly: 4 of 4, 3 of 3 and 3 of 3 identical.

The kit adds extensions. An extension registers micromark syntax, mdast handlers, hast handlers and editor behaviour together, so the same declaration serves the renderer, the editor and a server that only compiles the document (extensions). @react-markdown-kit/mermaid, @react-markdown-kit/slides and @react-markdown-kit/template are built this way. react-markdown has no equivalent, because it has no editor to share with.

One warning applies to both and is easy to miss: a plugin that changes the dialect cannot apply to a document that was already compiled without it. Compile with the extensions you render with, or pass the string (compiling a document).

Kit, if you need an editor or a diagram plugin. Even otherwise.

Migration

Two commands, both local, both shipped in the renderer package.

npx rmk-compare 'content/**/*.md' --gfm # diff your own corpus through both
npx rmk-migrate 'src/**/*.tsx' # report
npx rmk-migrate 'src/**/*.tsx' --write # apply

rmk-migrate edits import statements and nothing else, preserves your alias and quote style, and refuses to touch MarkdownAsync, MarkdownHooks and react-markdown/lib/*. It flags rehype-raw without a sanitizer instead of carrying that setup across. Every refusal is a test in tests/codemod.test.ts, and rmk-compare exits non-zero on a difference (tests/compare-runner.test.ts).

Three things the codemod cannot fix are listed in the alternative page and step by step in the migration guide. This is not a drop-in replacement.

FAQ

Which is smaller, react-markdown or React Markdown Kit?
react-markdown. One import of react-markdown 10.1.0 is smaller gzipped than one import of @react-markdown-kit/renderer 0.1.0, and the gap is about the same with GFM on both sides. Both parse with micromark; the difference is the kit document contract and policy layer. The numbers are in docs/data/bundle-sizes.json.
Do both render raw HTML safely by default?
Yes. Neither uses dangerouslySetInnerHTML, both render raw HTML as visible text unless you add rehype-raw, and both empty javascript: URLs with the same default urlTransform algorithm. 39 of 39 compared cases across 11 props produce identical markup.
Can I switch from react-markdown without rewriting my props?
The prop names and meanings match, and npx rmk-migrate rewrites the import statements only. Three things still differ: className is ignored rather than rejected, MarkdownAsync and MarkdownHooks do not exist, and a dialect plugin cannot apply to a document compiled without it. This is not a drop-in replacement.

Next

Renderer playground · @react-markdown-kit/renderer on npm · Source on GitHub

react-markdown alternative · vs markdown-to-jsx · vs Streamdown · Migration guide · Compatibility matrix · Security model